1 // FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY
  2 (function(win) {
  3 	var whiteSpaceRe = /^\s*|\s*$/g,
  4 		undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
  5 
  6 	var tinymce = {
  7 		majorVersion : '3',
  8 
  9 		minorVersion : '5.4.1',
 10 
 11 		releaseDate : '2012-06-24',
 12 
 13 		_init : function() {
 14 			var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
 15 
 16 			t.isOpera = win.opera && opera.buildNumber;
 17 
 18 			t.isWebKit = /WebKit/.test(ua);
 19 
 20 			t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
 21 
 22 			t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
 23 
 24 			t.isIE7 = t.isIE && /MSIE [7]/.test(ua);
 25 
 26 			t.isIE8 = t.isIE && /MSIE [8]/.test(ua);
 27 
 28 			t.isIE9 = t.isIE && /MSIE [9]/.test(ua);
 29 
 30 			t.isGecko = !t.isWebKit && /Gecko/.test(ua);
 31 
 32 			t.isMac = ua.indexOf('Mac') != -1;
 33 
 34 			t.isAir = /adobeair/i.test(ua);
 35 
 36 			t.isIDevice = /(iPad|iPhone)/.test(ua);
 37 			
 38 			t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;
 39 
 40 			// TinyMCE .NET webcontrol might be setting the values for TinyMCE
 41 			if (win.tinyMCEPreInit) {
 42 				t.suffix = tinyMCEPreInit.suffix;
 43 				t.baseURL = tinyMCEPreInit.base;
 44 				t.query = tinyMCEPreInit.query;
 45 				return;
 46 			}
 47 
 48 			// Get suffix and base
 49 			t.suffix = '';
 50 
 51 			// If base element found, add that infront of baseURL
 52 			nl = d.getElementsByTagName('base');
 53 			for (i=0; i<nl.length; i++) {
 54 				v = nl[i].href;
 55 				if (v) {
 56 					// Host only value like http://site.com or http://site.com:8008
 57 					if (/^https?:\/\/[^\/]+$/.test(v))
 58 						v += '/';
 59 
 60 					base = v ? v.match(/.*\//)[0] : ''; // Get only directory
 61 				}
 62 			}
 63 
 64 			function getBase(n) {
 65 				if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
 66 					if (/_(src|dev)\.js/g.test(n.src))
 67 						t.suffix = '_src';
 68 
 69 					if ((p = n.src.indexOf('?')) != -1)
 70 						t.query = n.src.substring(p + 1);
 71 
 72 					t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
 73 
 74 					// If path to script is relative and a base href was found add that one infront
 75 					// the src property will always be an absolute one on non IE browsers and IE 8
 76 					// so this logic will basically only be executed on older IE versions
 77 					if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
 78 						t.baseURL = base + t.baseURL;
 79 
 80 					return t.baseURL;
 81 				}
 82 
 83 				return null;
 84 			};
 85 
 86 			// Check document
 87 			nl = d.getElementsByTagName('script');
 88 			for (i=0; i<nl.length; i++) {
 89 				if (getBase(nl[i]))
 90 					return;
 91 			}
 92 
 93 			// Check head
 94 			n = d.getElementsByTagName('head')[0];
 95 			if (n) {
 96 				nl = n.getElementsByTagName('script');
 97 				for (i=0; i<nl.length; i++) {
 98 					if (getBase(nl[i]))
 99 						return;
100 				}
101 			}
102 
103 			return;
104 		},
105 
106 		is : function(o, t) {
107 			if (!t)
108 				return o !== undef;
109 
110 			if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
111 				return true;
112 
113 			return typeof(o) == t;
114 		},
115 
116 		makeMap : function(items, delim, map) {
117 			var i;
118 
119 			items = items || [];
120 			delim = delim || ',';
121 
122 			if (typeof(items) == "string")
123 				items = items.split(delim);
124 
125 			map = map || {};
126 
127 			i = items.length;
128 			while (i--)
129 				map[items[i]] = {};
130 
131 			return map;
132 		},
133 
134 		each : function(o, cb, s) {
135 			var n, l;
136 
137 			if (!o)
138 				return 0;
139 
140 			s = s || o;
141 
142 			if (o.length !== undef) {
143 				// Indexed arrays, needed for Safari
144 				for (n=0, l = o.length; n < l; n++) {
145 					if (cb.call(s, o[n], n, o) === false)
146 						return 0;
147 				}
148 			} else {
149 				// Hashtables
150 				for (n in o) {
151 					if (o.hasOwnProperty(n)) {
152 						if (cb.call(s, o[n], n, o) === false)
153 							return 0;
154 					}
155 				}
156 			}
157 
158 			return 1;
159 		},
160 
161 
162 		trim : function(s) {
163 			return (s ? '' + s : '').replace(whiteSpaceRe, '');
164 		},
165 
166 		create : function(s, p, root) {
167 			var t = this, sp, ns, cn, scn, c, de = 0;
168 
169 			// Parse : <prefix> <class>:<super class>
170 			s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
171 			cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
172 
173 			// Create namespace for new class
174 			ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
175 
176 			// Class already exists
177 			if (ns[cn])
178 				return;
179 
180 			// Make pure static class
181 			if (s[2] == 'static') {
182 				ns[cn] = p;
183 
184 				if (this.onCreate)
185 					this.onCreate(s[2], s[3], ns[cn]);
186 
187 				return;
188 			}
189 
190 			// Create default constructor
191 			if (!p[cn]) {
192 				p[cn] = function() {};
193 				de = 1;
194 			}
195 
196 			// Add constructor and methods
197 			ns[cn] = p[cn];
198 			t.extend(ns[cn].prototype, p);
199 
200 			// Extend
201 			if (s[5]) {
202 				sp = t.resolve(s[5]).prototype;
203 				scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
204 
205 				// Extend constructor
206 				c = ns[cn];
207 				if (de) {
208 					// Add passthrough constructor
209 					ns[cn] = function() {
210 						return sp[scn].apply(this, arguments);
211 					};
212 				} else {
213 					// Add inherit constructor
214 					ns[cn] = function() {
215 						this.parent = sp[scn];
216 						return c.apply(this, arguments);
217 					};
218 				}
219 				ns[cn].prototype[cn] = ns[cn];
220 
221 				// Add super methods
222 				t.each(sp, function(f, n) {
223 					ns[cn].prototype[n] = sp[n];
224 				});
225 
226 				// Add overridden methods
227 				t.each(p, function(f, n) {
228 					// Extend methods if needed
229 					if (sp[n]) {
230 						ns[cn].prototype[n] = function() {
231 							this.parent = sp[n];
232 							return f.apply(this, arguments);
233 						};
234 					} else {
235 						if (n != cn)
236 							ns[cn].prototype[n] = f;
237 					}
238 				});
239 			}
240 
241 			// Add static methods
242 			t.each(p['static'], function(f, n) {
243 				ns[cn][n] = f;
244 			});
245 
246 			if (this.onCreate)
247 				this.onCreate(s[2], s[3], ns[cn].prototype);
248 		},
249 
250 		walk : function(o, f, n, s) {
251 			s = s || this;
252 
253 			if (o) {
254 				if (n)
255 					o = o[n];
256 
257 				tinymce.each(o, function(o, i) {
258 					if (f.call(s, o, i, n) === false)
259 						return false;
260 
261 					tinymce.walk(o, f, n, s);
262 				});
263 			}
264 		},
265 
266 		createNS : function(n, o) {
267 			var i, v;
268 
269 			o = o || win;
270 
271 			n = n.split('.');
272 			for (i=0; i<n.length; i++) {
273 				v = n[i];
274 
275 				if (!o[v])
276 					o[v] = {};
277 
278 				o = o[v];
279 			}
280 
281 			return o;
282 		},
283 
284 		resolve : function(n, o) {
285 			var i, l;
286 
287 			o = o || win;
288 
289 			n = n.split('.');
290 			for (i = 0, l = n.length; i < l; i++) {
291 				o = o[n[i]];
292 
293 				if (!o)
294 					break;
295 			}
296 
297 			return o;
298 		},
299 
300 		addUnload : function(f, s) {
301 			var t = this, unload;
302 
303 			unload = function() {
304 				var li = t.unloads, o, n;
305 
306 				if (li) {
307 					// Call unload handlers
308 					for (n in li) {
309 						o = li[n];
310 
311 						if (o && o.func)
312 							o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
313 					}
314 
315 					// Detach unload function
316 					if (win.detachEvent) {
317 						win.detachEvent('onbeforeunload', fakeUnload);
318 						win.detachEvent('onunload', unload);
319 					} else if (win.removeEventListener)
320 						win.removeEventListener('unload', unload, false);
321 
322 					// Destroy references
323 					t.unloads = o = li = w = unload = 0;
324 
325 					// Run garbarge collector on IE
326 					if (win.CollectGarbage)
327 						CollectGarbage();
328 				}
329 			};
330 
331 			function fakeUnload() {
332 				var d = document;
333 
334 				function stop() {
335 					// Prevent memory leak
336 					d.detachEvent('onstop', stop);
337 
338 					// Call unload handler
339 					if (unload)
340 						unload();
341 
342 					d = 0;
343 				};
344 
345 				// Is there things still loading, then do some magic
346 				if (d.readyState == 'interactive') {
347 					// Fire unload when the currently loading page is stopped
348 					if (d)
349 						d.attachEvent('onstop', stop);
350 
351 					// Remove onstop listener after a while to prevent the unload function
352 					// to execute if the user presses cancel in an onbeforeunload
353 					// confirm dialog and then presses the browser stop button
354 					win.setTimeout(function() {
355 						if (d)
356 							d.detachEvent('onstop', stop);
357 					}, 0);
358 				}
359 			};
360 
361 			f = {func : f, scope : s || this};
362 
363 			if (!t.unloads) {
364 				// Attach unload handler
365 				if (win.attachEvent) {
366 					win.attachEvent('onunload', unload);
367 					win.attachEvent('onbeforeunload', fakeUnload);
368 				} else if (win.addEventListener)
369 					win.addEventListener('unload', unload, false);
370 
371 				// Setup initial unload handler array
372 				t.unloads = [f];
373 			} else
374 				t.unloads.push(f);
375 
376 			return f;
377 		},
378 
379 		removeUnload : function(f) {
380 			var u = this.unloads, r = null;
381 
382 			tinymce.each(u, function(o, i) {
383 				if (o && o.func == f) {
384 					u.splice(i, 1);
385 					r = f;
386 					return false;
387 				}
388 			});
389 
390 			return r;
391 		},
392 
393 		explode : function(s, d) {
394 			if (!s || tinymce.is(s, 'array')) {
395 				return s;
396 			}
397 
398 			return tinymce.map(s.split(d || ','), tinymce.trim);
399 		},
400 
401 		_addVer : function(u) {
402 			var v;
403 
404 			if (!this.query)
405 				return u;
406 
407 			v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
408 
409 			if (u.indexOf('#') == -1)
410 				return u + v;
411 
412 			return u.replace('#', v + '#');
413 		},
414 
415 		// Fix function for IE 9 where regexps isn't working correctly
416 		// Todo: remove me once MS fixes the bug
417 		_replace : function(find, replace, str) {
418 			// On IE9 we have to fake $x replacement
419 			if (isRegExpBroken) {
420 				return str.replace(find, function() {
421 					var val = replace, args = arguments, i;
422 
423 					for (i = 0; i < args.length - 2; i++) {
424 						if (args[i] === undef) {
425 							val = val.replace(new RegExp('\\$' + i, 'g'), '');
426 						} else {
427 							val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
428 						}
429 					}
430 
431 					return val;
432 				});
433 			}
434 
435 			return str.replace(find, replace);
436 		}
437 
438 		};
439 
440 	// Initialize the API
441 	tinymce._init();
442 
443 	// Expose tinymce namespace to the global namespace (window)
444 	win.tinymce = win.tinyMCE = tinymce;
445 
446 	// Describe the different namespaces
447 
448 	})(window);
449 
450 
451 (function($, tinymce) {
452 	var is = tinymce.is, attrRegExp = /^(href|src|style)$/i, undef;
453 
454 	// jQuery is undefined
455 	if (!$ && window.console) {
456 		return console.log("Load jQuery first!");
457 	}
458 
459 	// Stick jQuery into the tinymce namespace
460 	tinymce.$ = $;
461 
462 	// Setup adapter
463 	tinymce.adapter = {
464 		patchEditor : function(editor) {
465 			var fn = $.fn;
466 
467 			// Adapt the css function to make sure that the data-mce-style
468 			// attribute gets updated with the new style information
469 			function css(name, value) {
470 				var self = this;
471 
472 				// Remove data-mce-style when set operation occurs
473 				if (value)
474 					self.removeAttr('data-mce-style');
475 
476 				return fn.css.apply(self, arguments);
477 			};
478 
479 			// Apapt the attr function to make sure that it uses the data-mce- prefixed variants
480 			function attr(name, value) {
481 				var self = this;
482 
483 				// Update/retrive data-mce- attribute variants
484 				if (attrRegExp.test(name)) {
485 					if (value !== undef) {
486 						// Use TinyMCE behavior when setting the specifc attributes
487 						self.each(function(i, node) {
488 							editor.dom.setAttrib(node, name, value);
489 						});
490 
491 						return self;
492 					} else
493 						return self.attr('data-mce-' + name);
494 				}
495 
496 				// Default behavior
497 				return fn.attr.apply(self, arguments);
498 			};
499 
500 			// Patch various jQuery functions to handle tinymce specific attribute and content behavior
501 			// we don't patch the jQuery.fn directly since it will most likely break compatibility
502 			// with other jQuery logic on the page. Only instances created by TinyMCE should be patched.
503 			function patch(jq) {
504 				// Patch some functions, only patch the object once
505 				if (jq.css !== css) {
506 					// Patch css/attr to use the data-mce- prefixed attribute variants
507 					jq.css = css;
508 					jq.attr = attr;
509 
510 					jq.tinymce = editor;
511 
512 					// Each pushed jQuery instance needs to be patched
513 					// as well for example when traversing the DOM
514 					jq.pushStack = function() {
515 						return patch(fn.pushStack.apply(this, arguments));
516 					};
517 				}
518 
519 				return jq;
520 			};
521 
522 			// Add a $ function on each editor instance this one is scoped for the editor document object
523 			// this way you can do chaining like this tinymce.get(0).$('p').append('text').css('color', 'red');
524 			editor.$ = function(selector, scope) {
525 				var doc = editor.getDoc();
526 
527 				return patch($(selector || doc, doc || scope));
528 			};
529 		}
530 	};
531 
532 	// Patch in core NS functions
533 	tinymce.extend = $.extend;
534 	tinymce.extend(tinymce, {
535 		map : $.map,
536 		grep : function(a, f) {return $.grep(a, f || function(){return 1;});},
537 		inArray : function(a, v) {return $.inArray(v, a || []);}
538 
539 		/* Didn't iterate stylesheets
540 		each : function(o, cb, s) {
541 			if (!o)
542 				return 0;
543 
544 			var r = 1;
545 
546 			$.each(o, function(nr, el){
547 				if (cb.call(s, el, nr, o) === false) {
548 					r = 0;
549 					return false;
550 				}
551 			});
552 
553 			return r;
554 		}*/
555 	});
556 
557 	// Patch in functions in various clases
558 	// Add a "#ifndefjquery" statement around each core API function you add below
559 	var patches = {
560 		'tinymce.dom.DOMUtils' : {
561 			/*
562 			addClass : function(e, c) {
563 				if (is(e, 'array') && is(e[0], 'string'))
564 					e = e.join(',#');
565 				return (e && $(is(e, 'string') ? '#' + e : e)
566 					.addClass(c)
567 					.attr('class')) || false;
568 			},
569 
570 			hasClass : function(n, c) {
571 				return $(is(n, 'string') ? '#' + n : n).hasClass(c);
572 			},
573 
574 			removeClass : function(e, c) {
575 				if (!e)
576 					return false;
577 
578 				var r = [];
579 
580 				$(is(e, 'string') ? '#' + e : e)
581 					.removeClass(c)
582 					.each(function(){
583 						r.push(this.className);
584 					});
585 
586 				return r.length == 1 ? r[0] : r;
587 			},
588 			*/
589 
590 			select : function(pattern, scope) {
591 				var t = this;
592 
593 				return $.find(pattern, t.get(scope) || t.get(t.settings.root_element) || t.doc, []);
594 			},
595 
596 			is : function(n, patt) {
597 				return $(this.get(n)).is(patt);
598 			}
599 
600 			/*
601 			show : function(e) {
602 				if (is(e, 'array') && is(e[0], 'string'))
603 					e = e.join(',#');
604 
605 				$(is(e, 'string') ? '#' + e : e).css('display', 'block');
606 			},
607 
608 			hide : function(e) {
609 				if (is(e, 'array') && is(e[0], 'string'))
610 					e = e.join(',#');
611 
612 				$(is(e, 'string') ? '#' + e : e).css('display', 'none');
613 			},
614 
615 			isHidden : function(e) {
616 				return $(is(e, 'string') ? '#' + e : e).is(':hidden');
617 			},
618 
619 			insertAfter : function(n, e) {
620 				return $(is(e, 'string') ? '#' + e : e).after(n);
621 			},
622 
623 			replace : function(o, n, k) {
624 				n = $(is(n, 'string') ? '#' + n : n);
625 
626 				if (k)
627 					n.children().appendTo(o);
628 
629 				n.replaceWith(o);
630 			},
631 
632 			setStyle : function(n, na, v) {
633 				if (is(n, 'array') && is(n[0], 'string'))
634 					n = n.join(',#');
635 
636 				$(is(n, 'string') ? '#' + n : n).css(na, v);
637 			},
638 
639 			getStyle : function(n, na, c) {
640 				return $(is(n, 'string') ? '#' + n : n).css(na);
641 			},
642 
643 			setStyles : function(e, o) {
644 				if (is(e, 'array') && is(e[0], 'string'))
645 					e = e.join(',#');
646 				$(is(e, 'string') ? '#' + e : e).css(o);
647 			},
648 
649 			setAttrib : function(e, n, v) {
650 				var t = this, s = t.settings;
651 
652 				if (is(e, 'array') && is(e[0], 'string'))
653 					e = e.join(',#');
654 
655 				e = $(is(e, 'string') ? '#' + e : e);
656 
657 				switch (n) {
658 					case "style":
659 						e.each(function(i, v){
660 							if (s.keep_values)
661 								$(v).attr('data-mce-style', v);
662 
663 							v.style.cssText = v;
664 						});
665 						break;
666 
667 					case "class":
668 						e.each(function(){
669 							this.className = v;
670 						});
671 						break;
672 
673 					case "src":
674 					case "href":
675 						e.each(function(i, v){
676 							if (s.keep_values) {
677 								if (s.url_converter)
678 									v = s.url_converter.call(s.url_converter_scope || t, v, n, v);
679 
680 								t.setAttrib(v, 'data-mce-' + n, v);
681 							}
682 						});
683 
684 						break;
685 				}
686 
687 				if (v !== null && v.length !== 0)
688 					e.attr(n, '' + v);
689 				else
690 					e.removeAttr(n);
691 			},
692 
693 			setAttribs : function(e, o) {
694 				var t = this;
695 
696 				$.each(o, function(n, v){
697 					t.setAttrib(e,n,v);
698 				});
699 			}
700 			*/
701 		}
702 
703 /*
704 		'tinymce.dom.Event' : {
705 			add : function (o, n, f, s) {
706 				var lo, cb;
707 
708 				cb = function(e) {
709 					e.target = e.target || this;
710 					f.call(s || this, e);
711 				};
712 
713 				if (is(o, 'array') && is(o[0], 'string'))
714 					o = o.join(',#');
715 				o = $(is(o, 'string') ? '#' + o : o);
716 				if (n == 'init') {
717 					o.ready(cb, s);
718 				} else {
719 					if (s) {
720 						o.bind(n, s, cb);
721 					} else {
722 						o.bind(n, cb);
723 					}
724 				}
725 
726 				lo = this._jqLookup || (this._jqLookup = []);
727 				lo.push({func : f, cfunc : cb});
728 
729 				return cb;
730 			},
731 
732 			remove : function(o, n, f) {
733 				// Find cfunc
734 				$(this._jqLookup).each(function() {
735 					if (this.func === f)
736 						f = this.cfunc;
737 				});
738 
739 				if (is(o, 'array') && is(o[0], 'string'))
740 					o = o.join(',#');
741 
742 				$(is(o, 'string') ? '#' + o : o).unbind(n,f);
743 
744 				return true;
745 			}
746 		}
747 */
748 	};
749 
750 	// Patch functions after a class is created
751 	tinymce.onCreate = function(ty, c, p) {
752 		tinymce.extend(p, patches[c]);
753 	};
754 })(window.jQuery, tinymce);
755 
756 
757 
758 tinymce.create('tinymce.util.Dispatcher', {
759 	scope : null,
760 	listeners : null,
761 	inDispatch: false,
762 
763 	Dispatcher : function(scope) {
764 		this.scope = scope || this;
765 		this.listeners = [];
766 	},
767 
768 	add : function(callback, scope) {
769 		this.listeners.push({cb : callback, scope : scope || this.scope});
770 
771 		return callback;
772 	},
773 
774 	addToTop : function(callback, scope) {
775 		var self = this, listener = {cb : callback, scope : scope || self.scope};
776 
777 		// Create new listeners if addToTop is executed in a dispatch loop
778 		if (self.inDispatch) {
779 			self.listeners = [listener].concat(self.listeners);
780 		} else {
781 			self.listeners.unshift(listener);
782 		}
783 
784 		return callback;
785 	},
786 
787 	remove : function(callback) {
788 		var listeners = this.listeners, output = null;
789 
790 		tinymce.each(listeners, function(listener, i) {
791 			if (callback == listener.cb) {
792 				output = listener;
793 				listeners.splice(i, 1);
794 				return false;
795 			}
796 		});
797 
798 		return output;
799 	},
800 
801 	dispatch : function() {
802 		var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener;
803 
804 		self.inDispatch = true;
805 		
806 		// Needs to be a real loop since the listener count might change while looping
807 		// And this is also more efficient
808 		for (i = 0; i < listeners.length; i++) {
809 			listener = listeners[i];
810 			returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]);
811 
812 			if (returnValue === false)
813 				break;
814 		}
815 
816 		self.inDispatch = false;
817 
818 		return returnValue;
819 	}
820 
821 	});
822 
823 (function() {
824 	var each = tinymce.each;
825 
826 	tinymce.create('tinymce.util.URI', {
827 		URI : function(u, s) {
828 			var t = this, o, a, b, base_url;
829 
830 			// Trim whitespace
831 			u = tinymce.trim(u);
832 
833 			// Default settings
834 			s = t.settings = s || {};
835 
836 			// Strange app protocol that isn't http/https or local anchor
837 			// For example: mailto,skype,tel etc.
838 			if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {
839 				t.source = u;
840 				return;
841 			}
842 
843 			// Absolute path with no host, fake host and protocol
844 			if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
845 				u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
846 
847 			// Relative path http:// or protocol relative //path
848 			if (!/^[\w\-]*:?\/\//.test(u)) {
849 				base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
850 				u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
851 			}
852 
853 			// Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
854 			u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
855 			u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
856 			each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
857 				var s = u[i];
858 
859 				// Zope 3 workaround, they use @@something
860 				if (s)
861 					s = s.replace(/\(mce_at\)/g, '@@');
862 
863 				t[v] = s;
864 			});
865 
866 			b = s.base_uri;
867 			if (b) {
868 				if (!t.protocol)
869 					t.protocol = b.protocol;
870 
871 				if (!t.userInfo)
872 					t.userInfo = b.userInfo;
873 
874 				if (!t.port && t.host === 'mce_host')
875 					t.port = b.port;
876 
877 				if (!t.host || t.host === 'mce_host')
878 					t.host = b.host;
879 
880 				t.source = '';
881 			}
882 
883 			//t.path = t.path || '/';
884 		},
885 
886 		setPath : function(p) {
887 			var t = this;
888 
889 			p = /^(.*?)\/?(\w+)?$/.exec(p);
890 
891 			// Update path parts
892 			t.path = p[0];
893 			t.directory = p[1];
894 			t.file = p[2];
895 
896 			// Rebuild source
897 			t.source = '';
898 			t.getURI();
899 		},
900 
901 		toRelative : function(u) {
902 			var t = this, o;
903 
904 			if (u === "./")
905 				return u;
906 
907 			u = new tinymce.util.URI(u, {base_uri : t});
908 
909 			// Not on same domain/port or protocol
910 			if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
911 				return u.getURI();
912 
913 			var tu = t.getURI(), uu = u.getURI();
914 			
915 			// Allow usage of the base_uri when relative_urls = true
916 			if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu))
917 				return tu;
918 
919 			o = t.toRelPath(t.path, u.path);
920 
921 			// Add query
922 			if (u.query)
923 				o += '?' + u.query;
924 
925 			// Add anchor
926 			if (u.anchor)
927 				o += '#' + u.anchor;
928 
929 			return o;
930 		},
931 	
932 		toAbsolute : function(u, nh) {
933 			u = new tinymce.util.URI(u, {base_uri : this});
934 
935 			return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
936 		},
937 
938 		toRelPath : function(base, path) {
939 			var items, bp = 0, out = '', i, l;
940 
941 			// Split the paths
942 			base = base.substring(0, base.lastIndexOf('/'));
943 			base = base.split('/');
944 			items = path.split('/');
945 
946 			if (base.length >= items.length) {
947 				for (i = 0, l = base.length; i < l; i++) {
948 					if (i >= items.length || base[i] != items[i]) {
949 						bp = i + 1;
950 						break;
951 					}
952 				}
953 			}
954 
955 			if (base.length < items.length) {
956 				for (i = 0, l = items.length; i < l; i++) {
957 					if (i >= base.length || base[i] != items[i]) {
958 						bp = i + 1;
959 						break;
960 					}
961 				}
962 			}
963 
964 			if (bp === 1)
965 				return path;
966 
967 			for (i = 0, l = base.length - (bp - 1); i < l; i++)
968 				out += "../";
969 
970 			for (i = bp - 1, l = items.length; i < l; i++) {
971 				if (i != bp - 1)
972 					out += "/" + items[i];
973 				else
974 					out += items[i];
975 			}
976 
977 			return out;
978 		},
979 
980 		toAbsPath : function(base, path) {
981 			var i, nb = 0, o = [], tr, outPath;
982 
983 			// Split paths
984 			tr = /\/$/.test(path) ? '/' : '';
985 			base = base.split('/');
986 			path = path.split('/');
987 
988 			// Remove empty chunks
989 			each(base, function(k) {
990 				if (k)
991 					o.push(k);
992 			});
993 
994 			base = o;
995 
996 			// Merge relURLParts chunks
997 			for (i = path.length - 1, o = []; i >= 0; i--) {
998 				// Ignore empty or .
999 				if (path[i].length === 0 || path[i] === ".")
1000 					continue;
1001 
1002 				// Is parent
1003 				if (path[i] === '..') {
1004 					nb++;
1005 					continue;
1006 				}
1007 
1008 				// Move up
1009 				if (nb > 0) {
1010 					nb--;
1011 					continue;
1012 				}
1013 
1014 				o.push(path[i]);
1015 			}
1016 
1017 			i = base.length - nb;
1018 
1019 			// If /a/b/c or /
1020 			if (i <= 0)
1021 				outPath = o.reverse().join('/');
1022 			else
1023 				outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
1024 
1025 			// Add front / if it's needed
1026 			if (outPath.indexOf('/') !== 0)
1027 				outPath = '/' + outPath;
1028 
1029 			// Add traling / if it's needed
1030 			if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
1031 				outPath += tr;
1032 
1033 			return outPath;
1034 		},
1035 
1036 		getURI : function(nh) {
1037 			var s, t = this;
1038 
1039 			// Rebuild source
1040 			if (!t.source || nh) {
1041 				s = '';
1042 
1043 				if (!nh) {
1044 					if (t.protocol)
1045 						s += t.protocol + '://';
1046 
1047 					if (t.userInfo)
1048 						s += t.userInfo + '@';
1049 
1050 					if (t.host)
1051 						s += t.host;
1052 
1053 					if (t.port)
1054 						s += ':' + t.port;
1055 				}
1056 
1057 				if (t.path)
1058 					s += t.path;
1059 
1060 				if (t.query)
1061 					s += '?' + t.query;
1062 
1063 				if (t.anchor)
1064 					s += '#' + t.anchor;
1065 
1066 				t.source = s;
1067 			}
1068 
1069 			return t.source;
1070 		}
1071 	});
1072 })();
1073 
1074 (function() {
1075 	var each = tinymce.each;
1076 
1077 	tinymce.create('static tinymce.util.Cookie', {
1078 		getHash : function(n) {
1079 			var v = this.get(n), h;
1080 
1081 			if (v) {
1082 				each(v.split('&'), function(v) {
1083 					v = v.split('=');
1084 					h = h || {};
1085 					h[unescape(v[0])] = unescape(v[1]);
1086 				});
1087 			}
1088 
1089 			return h;
1090 		},
1091 
1092 		setHash : function(n, v, e, p, d, s) {
1093 			var o = '';
1094 
1095 			each(v, function(v, k) {
1096 				o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
1097 			});
1098 
1099 			this.set(n, o, e, p, d, s);
1100 		},
1101 
1102 		get : function(n) {
1103 			var c = document.cookie, e, p = n + "=", b;
1104 
1105 			// Strict mode
1106 			if (!c)
1107 				return;
1108 
1109 			b = c.indexOf("; " + p);
1110 
1111 			if (b == -1) {
1112 				b = c.indexOf(p);
1113 
1114 				if (b !== 0)
1115 					return null;
1116 			} else
1117 				b += 2;
1118 
1119 			e = c.indexOf(";", b);
1120 
1121 			if (e == -1)
1122 				e = c.length;
1123 
1124 			return unescape(c.substring(b + p.length, e));
1125 		},
1126 
1127 		set : function(n, v, e, p, d, s) {
1128 			document.cookie = n + "=" + escape(v) +
1129 				((e) ? "; expires=" + e.toGMTString() : "") +
1130 				((p) ? "; path=" + escape(p) : "") +
1131 				((d) ? "; domain=" + d : "") +
1132 				((s) ? "; secure" : "");
1133 		},
1134 
1135 		remove : function(name, path, domain) {
1136 			var date = new Date();
1137 
1138 			date.setTime(date.getTime() - 1000);
1139 
1140 			this.set(name, '', date, path, domain);
1141 		}
1142 	});
1143 })();
1144 
1145 (function() {
1146 	function serialize(o, quote) {
1147 		var i, v, t, name;
1148 
1149 		quote = quote || '"';
1150 
1151 		if (o == null)
1152 			return 'null';
1153 
1154 		t = typeof o;
1155 
1156 		if (t == 'string') {
1157 			v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
1158 
1159 			return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
1160 				// Make sure single quotes never get encoded inside double quotes for JSON compatibility
1161 				if (quote === '"' && a === "'")
1162 					return a;
1163 
1164 				i = v.indexOf(b);
1165 
1166 				if (i + 1)
1167 					return '\\' + v.charAt(i + 1);
1168 
1169 				a = b.charCodeAt().toString(16);
1170 
1171 				return '\\u' + '0000'.substring(a.length) + a;
1172 			}) + quote;
1173 		}
1174 
1175 		if (t == 'object') {
1176 			if (o.hasOwnProperty && o instanceof Array) {
1177 					for (i=0, v = '['; i<o.length; i++)
1178 						v += (i > 0 ? ',' : '') + serialize(o[i], quote);
1179 
1180 					return v + ']';
1181 				}
1182 
1183 				v = '{';
1184 
1185 				for (name in o) {
1186 					if (o.hasOwnProperty(name)) {
1187 						v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : '';
1188 					}
1189 				}
1190 
1191 				return v + '}';
1192 		}
1193 
1194 		return '' + o;
1195 	};
1196 
1197 	tinymce.util.JSON = {
1198 		serialize: serialize,
1199 
1200 		parse: function(s) {
1201 			try {
1202 				return eval('(' + s + ')');
1203 			} catch (ex) {
1204 				// Ignore
1205 			}
1206 		}
1207 
1208 		};
1209 })();
1210 
1211 tinymce.create('static tinymce.util.XHR', {
1212 	send : function(o) {
1213 		var x, t, w = window, c = 0;
1214 
1215 		function ready() {
1216 			if (!o.async || x.readyState == 4 || c++ > 10000) {
1217 				if (o.success && c < 10000 && x.status == 200)
1218 					o.success.call(o.success_scope, '' + x.responseText, x, o);
1219 				else if (o.error)
1220 					o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
1221 
1222 				x = null;
1223 			} else
1224 				w.setTimeout(ready, 10);
1225 		};
1226 
1227 		// Default settings
1228 		o.scope = o.scope || this;
1229 		o.success_scope = o.success_scope || o.scope;
1230 		o.error_scope = o.error_scope || o.scope;
1231 		o.async = o.async === false ? false : true;
1232 		o.data = o.data || '';
1233 
1234 		function get(s) {
1235 			x = 0;
1236 
1237 			try {
1238 				x = new ActiveXObject(s);
1239 			} catch (ex) {
1240 			}
1241 
1242 			return x;
1243 		};
1244 
1245 		x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
1246 
1247 		if (x) {
1248 			if (x.overrideMimeType)
1249 				x.overrideMimeType(o.content_type);
1250 
1251 			x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
1252 
1253 			if (o.content_type)
1254 				x.setRequestHeader('Content-Type', o.content_type);
1255 
1256 			x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
1257 
1258 			x.send(o.data);
1259 
1260 			// Syncronous request
1261 			if (!o.async)
1262 				return ready();
1263 
1264 			// Wait for response, onReadyStateChange can not be used since it leaks memory in IE
1265 			t = w.setTimeout(ready, 10);
1266 		}
1267 	}
1268 });
1269 
1270 (function() {
1271 	var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
1272 
1273 	tinymce.create('tinymce.util.JSONRequest', {
1274 		JSONRequest : function(s) {
1275 			this.settings = extend({
1276 			}, s);
1277 			this.count = 0;
1278 		},
1279 
1280 		send : function(o) {
1281 			var ecb = o.error, scb = o.success;
1282 
1283 			o = extend(this.settings, o);
1284 
1285 			o.success = function(c, x) {
1286 				c = JSON.parse(c);
1287 
1288 				if (typeof(c) == 'undefined') {
1289 					c = {
1290 						error : 'JSON Parse error.'
1291 					};
1292 				}
1293 
1294 				if (c.error)
1295 					ecb.call(o.error_scope || o.scope, c.error, x);
1296 				else
1297 					scb.call(o.success_scope || o.scope, c.result);
1298 			};
1299 
1300 			o.error = function(ty, x) {
1301 				if (ecb)
1302 					ecb.call(o.error_scope || o.scope, ty, x);
1303 			};
1304 
1305 			o.data = JSON.serialize({
1306 				id : o.id || 'c' + (this.count++),
1307 				method : o.method,
1308 				params : o.params
1309 			});
1310 
1311 			// JSON content type for Ruby on rails. Bug: #1883287
1312 			o.content_type = 'application/json';
1313 
1314 			XHR.send(o);
1315 		},
1316 
1317 		'static' : {
1318 			sendRPC : function(o) {
1319 				return new tinymce.util.JSONRequest().send(o);
1320 			}
1321 		}
1322 	});
1323 }());
1324 (function(tinymce){
1325 	tinymce.VK = {
1326 		BACKSPACE: 8,
1327 		DELETE: 46,
1328 		DOWN: 40,
1329 		ENTER: 13,
1330 		LEFT: 37,
1331 		RIGHT: 39,
1332 		SPACEBAR: 32,
1333 		TAB: 9,
1334 		UP: 38,
1335 
1336 		modifierPressed: function (e) {
1337 			return e.shiftKey || e.ctrlKey || e.altKey;
1338 		},
1339 
1340 		metaKeyPressed: function(e) {
1341 			return tinymce.isMac ? e.metaKey : e.ctrlKey;
1342 		}
1343 	};
1344 })(tinymce);
1345 
1346 tinymce.util.Quirks = function(editor) {
1347 	var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, settings = editor.settings;
1348 
1349 	function setEditorCommandState(cmd, state) {
1350 		try {
1351 			editor.getDoc().execCommand(cmd, false, state);
1352 		} catch (ex) {
1353 			// Ignore
1354 		}
1355 	}
1356 
1357 	function getDocumentMode() {
1358 		var documentMode = editor.getDoc().documentMode;
1359 
1360 		return documentMode ? documentMode : 6;
1361 	};
1362 
1363 	function cleanupStylesWhenDeleting() {
1364 		function removeMergedFormatSpans(isDelete) {
1365 			var rng, blockElm, node, clonedSpan;
1366 
1367 			rng = selection.getRng();
1368 
1369 			// Find root block
1370 			blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1371 
1372 			// On delete clone the root span of the next block element
1373 			if (isDelete)
1374 				blockElm = dom.getNext(blockElm, dom.isBlock);
1375 
1376 			// Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace
1377 			if (blockElm) {
1378 				node = blockElm.firstChild;
1379 
1380 				// Ignore empty text nodes
1381 				while (node && node.nodeType == 3 && node.nodeValue.length === 0)
1382 					node = node.nextSibling;
1383 
1384 				if (node && node.nodeName === 'SPAN') {
1385 					clonedSpan = node.cloneNode(false);
1386 				}
1387 			}
1388 
1389 			// Do the backspace/delete action
1390 			editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1391 
1392 			// Find all odd apple-style-spans
1393 			blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1394 			tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) {
1395 				var bm = selection.getBookmark();
1396 
1397 				if (clonedSpan) {
1398 					dom.replace(clonedSpan.cloneNode(false), span, true);
1399 				} else {
1400 					dom.remove(span, true);
1401 				}
1402 
1403 				// Restore the selection
1404 				selection.moveToBookmark(bm);
1405 			});
1406 		};
1407 
1408 		editor.onKeyDown.add(function(editor, e) {
1409 			var isDelete;
1410 
1411 			isDelete = e.keyCode == DELETE;
1412 			if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1413 				e.preventDefault();
1414 				removeMergedFormatSpans(isDelete);
1415 			}
1416 		});
1417 
1418 		editor.addCommand('Delete', function() {removeMergedFormatSpans();});
1419 	};
1420 	
1421 	function emptyEditorWhenDeleting() {
1422 		function serializeRng(rng) {
1423 			var body = dom.create("body");
1424 			var contents = rng.cloneContents();
1425 			body.appendChild(contents);
1426 			return selection.serializer.serialize(body, {format: 'html'});
1427 		}
1428 
1429 		function allContentsSelected(rng) {
1430 			var selection = serializeRng(rng);
1431 
1432 			var allRng = dom.createRng();
1433 			allRng.selectNode(editor.getBody());
1434 
1435 			var allSelection = serializeRng(allRng);//console.log(selection, "----", allSelection);
1436 			return selection === allSelection;
1437 		}
1438 
1439 		editor.onKeyDown.add(function(editor, e) {
1440 			var keyCode = e.keyCode, isCollapsed;
1441 
1442 			// Empty the editor if it's needed for example backspace at <p><b>|</b></p>
1443 			if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) {
1444 				isCollapsed = editor.selection.isCollapsed();
1445 
1446 				// Selection is collapsed but the editor isn't empty
1447 				if (isCollapsed && !dom.isEmpty(editor.getBody())) {
1448 					return;
1449 				}
1450 
1451 				// IE deletes all contents correctly when everything is selected
1452 				if (tinymce.isIE && !isCollapsed) {
1453 					return;
1454 				}
1455 
1456 				// Selection isn't collapsed but not all the contents is selected
1457 				if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
1458 					return;
1459 				}
1460 
1461 				// Manually empty the editor
1462 				editor.setContent('');
1463 				editor.selection.setCursorLocation(editor.getBody(), 0);
1464 				editor.nodeChanged();
1465 			}
1466 		});
1467 	};
1468 
1469 	function selectAll() {
1470 		editor.onKeyDown.add(function(editor, e) {
1471 			if (e.keyCode == 65 && VK.metaKeyPressed(e)) {
1472 				e.preventDefault();
1473 				editor.execCommand('SelectAll');
1474 			}
1475 		});
1476 	};
1477 
1478 	function inputMethodFocus() {
1479 		if (!editor.settings.content_editable) {
1480 			// Case 1 IME doesn't initialize if you focus the document
1481 			dom.bind(editor.getDoc(), 'focusin', function(e) {
1482 				selection.setRng(selection.getRng());
1483 			});
1484 
1485 			// Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
1486 			dom.bind(editor.getDoc(), 'mousedown', function(e) {
1487 				if (e.target == editor.getDoc().documentElement) {
1488 					editor.getWin().focus();
1489 					selection.setRng(selection.getRng());
1490 				}
1491 			});
1492 		}
1493 	};
1494 
1495 	function removeHrOnBackspace() {
1496 		editor.onKeyDown.add(function(editor, e) {
1497 			if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
1498 				if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1499 					var node = selection.getNode();
1500 					var previousSibling = node.previousSibling;
1501 
1502 					if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
1503 						dom.remove(previousSibling);
1504 						tinymce.dom.Event.cancel(e);
1505 					}
1506 				}
1507 			}
1508 		})
1509 	}
1510 
1511 	function focusBody() {
1512 		// Fix for a focus bug in FF 3.x where the body element
1513 		// wouldn't get proper focus if the user clicked on the HTML element
1514 		if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
1515 			editor.onMouseDown.add(function(editor, e) {
1516 				if (e.target.nodeName === "HTML") {
1517 					var body = editor.getBody();
1518 
1519 					// Blur the body it's focused but not correctly focused
1520 					body.blur();
1521 
1522 					// Refocus the body after a little while
1523 					setTimeout(function() {
1524 						body.focus();
1525 					}, 0);
1526 				}
1527 			});
1528 		}
1529 	};
1530 
1531 	function selectControlElements() {
1532 		editor.onClick.add(function(editor, e) {
1533 			e = e.target;
1534 
1535 			// Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
1536 			// WebKit can't even do simple things like selecting an image
1537 			// Needs tobe the setBaseAndExtend or it will fail to select floated images
1538 			if (/^(IMG|HR)$/.test(e.nodeName)) {
1539 				selection.getSel().setBaseAndExtent(e, 0, e, 1);
1540 			}
1541 
1542 			if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) {
1543 				selection.select(e);
1544 			}
1545 
1546 			editor.nodeChanged();
1547 		});
1548 	};
1549 
1550 	function removeStylesWhenDeletingAccrossBlockElements() {
1551 		function getAttributeApplyFunction() {
1552 			var template = dom.getAttribs(selection.getStart().cloneNode(false));
1553 
1554 			return function() {
1555 				var target = selection.getStart();
1556 
1557 				if (target !== editor.getBody()) {
1558 					dom.setAttrib(target, "style", null);
1559 
1560 					tinymce.each(template, function(attr) {
1561 						target.setAttributeNode(attr.cloneNode(true));
1562 					});
1563 				}
1564 			};
1565 		}
1566 
1567 		function isSelectionAcrossElements() {
1568 			return !selection.isCollapsed() && selection.getStart() != selection.getEnd();
1569 		}
1570 
1571 		function blockEvent(editor, e) {
1572 			e.preventDefault();
1573 			return false;
1574 		}
1575 
1576 		editor.onKeyPress.add(function(editor, e) {
1577 			var applyAttributes;
1578 
1579 			if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
1580 				applyAttributes = getAttributeApplyFunction();
1581 				editor.getDoc().execCommand('delete', false, null);
1582 				applyAttributes();
1583 				e.preventDefault();
1584 				return false;
1585 			}
1586 		});
1587 
1588 		dom.bind(editor.getDoc(), 'cut', function(e) {
1589 			var applyAttributes;
1590 
1591 			if (isSelectionAcrossElements()) {
1592 				applyAttributes = getAttributeApplyFunction();
1593 				editor.onKeyUp.addToTop(blockEvent);
1594 
1595 				setTimeout(function() {
1596 					applyAttributes();
1597 					editor.onKeyUp.remove(blockEvent);
1598 				}, 0);
1599 			}
1600 		});
1601 	}
1602 
1603 	function selectionChangeNodeChanged() {
1604 		var lastRng, selectionTimer;
1605 
1606 		dom.bind(editor.getDoc(), 'selectionchange', function() {
1607 			if (selectionTimer) {
1608 				clearTimeout(selectionTimer);
1609 				selectionTimer = 0;
1610 			}
1611 
1612 			selectionTimer = window.setTimeout(function() {
1613 				var rng = selection.getRng();
1614 
1615 				// Compare the ranges to see if it was a real change or not
1616 				if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) {
1617 					editor.nodeChanged();
1618 					lastRng = rng;
1619 				}
1620 			}, 50);
1621 		});
1622 	}
1623 
1624 	function ensureBodyHasRoleApplication() {
1625 		document.body.setAttribute("role", "application");
1626 	}
1627 
1628 	function disableBackspaceIntoATable() {
1629 		editor.onKeyDown.add(function(editor, e) {
1630 			if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
1631 				if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1632 					var previousSibling = selection.getNode().previousSibling;
1633 					if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
1634 						return tinymce.dom.Event.cancel(e);
1635 					}
1636 				}
1637 			}
1638 		})
1639 	}
1640 
1641 	function addNewLinesBeforeBrInPre() {
1642 		// IE8+ rendering mode does the right thing with BR in PRE
1643 		if (getDocumentMode() > 7) {
1644 			return;
1645 		}
1646 
1647 		 // Enable display: none in area and add a specific class that hides all BR elements in PRE to
1648 		 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
1649 		setEditorCommandState('RespectVisibilityInDesign', true);
1650 		editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
1651 		dom.addClass(editor.getBody(), 'mceHideBrInPre');
1652 
1653 		// Adds a \n before all BR elements in PRE to get them visual
1654 		editor.parser.addNodeFilter('pre', function(nodes, name) {
1655 			var i = nodes.length, brNodes, j, brElm, sibling;
1656 
1657 			while (i--) {
1658 				brNodes = nodes[i].getAll('br');
1659 				j = brNodes.length;
1660 				while (j--) {
1661 					brElm = brNodes[j];
1662 
1663 					// Add \n before BR in PRE elements on older IE:s so the new lines get rendered
1664 					sibling = brElm.prev;
1665 					if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
1666 						sibling.value += '\n';
1667 					} else {
1668 						brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n';
1669 					}
1670 				}
1671 			}
1672 		});
1673 
1674 		// Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
1675 		editor.serializer.addNodeFilter('pre', function(nodes, name) {
1676 			var i = nodes.length, brNodes, j, brElm, sibling;
1677 
1678 			while (i--) {
1679 				brNodes = nodes[i].getAll('br');
1680 				j = brNodes.length;
1681 				while (j--) {
1682 					brElm = brNodes[j];
1683 					sibling = brElm.prev;
1684 					if (sibling && sibling.type == 3) {
1685 						sibling.value = sibling.value.replace(/\r?\n$/, '');
1686 					}
1687 				}
1688 			}
1689 		});
1690 	}
1691 
1692 	function removePreSerializedStylesWhenSelectingControls() {
1693 		dom.bind(editor.getBody(), 'mouseup', function(e) {
1694 			var value, node = selection.getNode();
1695 
1696 			// Moved styles to attributes on IMG eements
1697 			if (node.nodeName == 'IMG') {
1698 				// Convert style width to width attribute
1699 				if (value = dom.getStyle(node, 'width')) {
1700 					dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
1701 					dom.setStyle(node, 'width', '');
1702 				}
1703 
1704 				// Convert style height to height attribute
1705 				if (value = dom.getStyle(node, 'height')) {
1706 					dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
1707 					dom.setStyle(node, 'height', '');
1708 				}
1709 			}
1710 		});
1711 	}
1712 
1713 	function keepInlineElementOnDeleteBackspace() {
1714 		editor.onKeyDown.add(function(editor, e) {
1715 			var isDelete, rng, container, offset, brElm, sibling, collapsed;
1716 
1717 			isDelete = e.keyCode == DELETE;
1718 			if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1719 				rng = selection.getRng();
1720 				container = rng.startContainer;
1721 				offset = rng.startOffset;
1722 				collapsed = rng.collapsed;
1723 
1724 				// Override delete if the start container is a text node and is at the beginning of text or
1725 				// just before/after the last character to be deleted in collapsed mode
1726 				if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) {
1727 					nonEmptyElements = editor.schema.getNonEmptyElements();
1728 
1729 					// Prevent default logic since it's broken
1730 					e.preventDefault();
1731 
1732 					// Insert a BR before the text node this will prevent the containing element from being deleted/converted
1733 					brElm = dom.create('br', {id: '__tmp'});
1734 					container.parentNode.insertBefore(brElm, container);
1735 
1736 					// Do the browser delete
1737 					editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1738 
1739 					// Check if the previous sibling is empty after deleting for example: <p><b></b>|</p>
1740 					container = selection.getRng().startContainer;
1741 					sibling = container.previousSibling;
1742 					if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) {
1743 						dom.remove(sibling);
1744 					}
1745 
1746 					// Remove the temp element we inserted
1747 					dom.remove('__tmp');
1748 				}
1749 			}
1750 		});
1751 	}
1752 
1753 	function removeBlockQuoteOnBackSpace() {
1754 		// Add block quote deletion handler
1755 		editor.onKeyDown.add(function(editor, e) {
1756 			var rng, container, offset, root, parent;
1757 
1758 			if (e.isDefaultPrevented() || e.keyCode != VK.BACKSPACE) {
1759 				return;
1760 			}
1761 
1762 			rng = selection.getRng();
1763 			container = rng.startContainer;
1764 			offset = rng.startOffset;
1765 			root = dom.getRoot();
1766 			parent = container;
1767 
1768 			if (!rng.collapsed || offset !== 0) {
1769 				return;
1770 			}
1771 
1772 			while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
1773 				parent = parent.parentNode;
1774 			}
1775 
1776 			// Is the cursor at the beginning of a blockquote?
1777 			if (parent.tagName === 'BLOCKQUOTE') {
1778 				// Remove the blockquote
1779 				editor.formatter.toggle('blockquote', null, parent);
1780 
1781 				// Move the caret to the beginning of container
1782 				rng.setStart(container, 0);
1783 				rng.setEnd(container, 0);
1784 				selection.setRng(rng);
1785 				selection.collapse(false);
1786 			}
1787 		});
1788 	};
1789 
1790 	function setGeckoEditingOptions() {
1791 		function setOpts() {
1792 			editor._refreshContentEditable();
1793 
1794 			setEditorCommandState("StyleWithCSS", false);
1795 			setEditorCommandState("enableInlineTableEditing", false);
1796 
1797 			if (!settings.object_resizing) {
1798 				setEditorCommandState("enableObjectResizing", false);
1799 			}
1800 		};
1801 
1802 		if (!settings.readonly) {
1803 			editor.onBeforeExecCommand.add(setOpts);
1804 			editor.onMouseDown.add(setOpts);
1805 		}
1806 	};
1807 
1808 	function addBrAfterLastLinks() {
1809 		function fixLinks(editor, o) {
1810 			tinymce.each(dom.select('a'), function(node) {
1811 				var parentNode = node.parentNode, root = dom.getRoot();
1812 
1813 				if (parentNode.lastChild === node) {
1814 					while (parentNode && !dom.isBlock(parentNode)) {
1815 						if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
1816 							return;
1817 						}
1818 
1819 						parentNode = parentNode.parentNode;
1820 					}
1821 
1822 					dom.add(parentNode, 'br', {'data-mce-bogus' : 1});
1823 				}
1824 			});
1825 		};
1826 
1827 		editor.onExecCommand.add(function(editor, cmd) {
1828 			if (cmd === 'CreateLink') {
1829 				fixLinks(editor);
1830 			}
1831 		});
1832 
1833 		editor.onSetContent.add(selection.onSetContent.add(fixLinks));
1834 	};
1835 
1836 	function setDefaultBlockType() {
1837 		if (settings.forced_root_block) {
1838 			editor.onInit.add(function() {
1839 				setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
1840 			});
1841 		}
1842 	}
1843 
1844 	function removeGhostSelection() {
1845 		function repaint(sender, args) {
1846 			if (!sender || !args.initial) {
1847 				editor.execCommand('mceRepaint');
1848 			}
1849 		};
1850 
1851 		editor.onUndo.add(repaint);
1852 		editor.onRedo.add(repaint);
1853 		editor.onSetContent.add(repaint);
1854 	};
1855 
1856 	function deleteControlItemOnBackSpace() {
1857 		editor.onKeyDown.add(function(editor, e) {
1858 			var rng;
1859 
1860 			if (!e.isDefaultPrevented() && e.keyCode == BACKSPACE) {
1861 				rng = editor.getDoc().selection.createRange();
1862 				if (rng && rng.item) {
1863 					e.preventDefault();
1864 					editor.undoManager.beforeChange();
1865 					dom.remove(rng.item(0));
1866 					editor.undoManager.add();
1867 				}
1868 			}
1869 		});
1870 	};
1871 
1872 	function renderEmptyBlocksFix() {
1873 		var emptyBlocksCSS;
1874 
1875 		// IE10+
1876 		if (getDocumentMode() >= 10) {
1877 			emptyBlocksCSS = '';
1878 			tinymce.each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
1879 				emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
1880 			});
1881 
1882 			editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
1883 		}
1884 	};
1885 
1886 	function fakeImageResize() {
1887 		var mouseDownImg, startX, startY, startW, startH;
1888 
1889 		if (!settings.object_resizing || settings.webkit_fake_resize === false) {
1890 			return;
1891 		}
1892 
1893 		editor.contentStyles.push('.mceResizeImages img {cursor: se-resize !important}');
1894 
1895 		function resizeImage(e) {
1896 			var deltaX, deltaY, ratio, width, height;
1897 
1898 			if (mouseDownImg) {
1899 				deltaX = e.screenX - startX;
1900 				deltaY = e.screenY - startY;
1901 				ratio = Math.max((startW + deltaX) / startW, (startH + deltaY) / startH);
1902 
1903 				// Only update styles if the user draged one pixel or more
1904 				if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) {
1905 					// Constrain proportions
1906 					width = Math.round(startW * ratio);
1907 					height = Math.round(startH * ratio);
1908 
1909 					// Resize by using style or attribute
1910 					if (mouseDownImg.style.width) {
1911 						dom.setStyle(mouseDownImg, 'width', width);
1912 					} else {
1913 						dom.setAttrib(mouseDownImg, 'width', width);
1914 					}
1915 
1916 					// Resize by using style or attribute
1917 					if (mouseDownImg.style.height) {
1918 						dom.setStyle(mouseDownImg, 'height', height);
1919 					} else {
1920 						dom.setAttrib(mouseDownImg, 'height', height);
1921 					}
1922 
1923 					if (!dom.hasClass(editor.getBody(), 'mceResizeImages')) {
1924 						dom.addClass(editor.getBody(), 'mceResizeImages');
1925 					}
1926 				}
1927 			}
1928 		};
1929 
1930 		editor.onMouseDown.add(function(editor, e) {
1931 			var target = e.target;
1932 
1933 			if (target.nodeName == "IMG") {
1934 				mouseDownImg = target;
1935 				startX = e.screenX;
1936 				startY = e.screenY;
1937 				startW = mouseDownImg.clientWidth;
1938 				startH = mouseDownImg.clientHeight;
1939 				dom.bind(editor.getDoc(), 'mousemove', resizeImage);
1940 				e.preventDefault();
1941 			}
1942 		});
1943 
1944 		// Unbind events on node change and restore resize cursor
1945 		editor.onNodeChange.add(function() {
1946 			if (mouseDownImg) {
1947 				mouseDownImg = null;
1948 				dom.unbind(editor.getDoc(), 'mousemove', resizeImage);
1949 			}
1950 
1951 			if (selection.getNode().nodeName == "IMG") {
1952 				dom.addClass(editor.getBody(), 'mceResizeImages');
1953 			} else {
1954 				dom.removeClass(editor.getBody(), 'mceResizeImages');
1955 			}
1956 		});
1957 	};
1958 
1959 	// All browsers
1960 	disableBackspaceIntoATable();
1961 	removeBlockQuoteOnBackSpace();
1962 	emptyEditorWhenDeleting();
1963 
1964 	// WebKit
1965 	if (tinymce.isWebKit) {
1966 		keepInlineElementOnDeleteBackspace();
1967 		cleanupStylesWhenDeleting();
1968 		inputMethodFocus();
1969 		selectControlElements();
1970 		setDefaultBlockType();
1971 
1972 		// iOS
1973 		if (tinymce.isIDevice) {
1974 			selectionChangeNodeChanged();
1975 		} else {
1976 			fakeImageResize();
1977 			selectAll();
1978 		}
1979 	}
1980 
1981 	// IE
1982 	if (tinymce.isIE) {
1983 		removeHrOnBackspace();
1984 		ensureBodyHasRoleApplication();
1985 		addNewLinesBeforeBrInPre();
1986 		removePreSerializedStylesWhenSelectingControls();
1987 		deleteControlItemOnBackSpace();
1988 		renderEmptyBlocksFix();
1989 	}
1990 
1991 	// Gecko
1992 	if (tinymce.isGecko) {
1993 		removeHrOnBackspace();
1994 		focusBody();
1995 		removeStylesWhenDeletingAccrossBlockElements();
1996 		setGeckoEditingOptions();
1997 		addBrAfterLastLinks();
1998 		removeGhostSelection();
1999 	}
2000 };
2001 (function(tinymce) {
2002 	var namedEntities, baseEntities, reverseEntities,
2003 		attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
2004 		textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
2005 		rawCharsRegExp = /[<>&\"\']/g,
2006 		entityRegExp = /&(#x|#)?([\w]+);/g,
2007 		asciiMap = {
2008 				128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
2009 				135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
2010 				142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
2011 				150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
2012 				156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
2013 		};
2014 
2015 	// Raw entities
2016 	baseEntities = {
2017 		'\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code
2018 		"'" : ''',
2019 		'<' : '<',
2020 		'>' : '>',
2021 		'&' : '&'
2022 	};
2023 
2024 	// Reverse lookup table for raw entities
2025 	reverseEntities = {
2026 		'<' : '<',
2027 		'>' : '>',
2028 		'&' : '&',
2029 		'"' : '"',
2030 		''' : "'"
2031 	};
2032 
2033 	// Decodes text by using the browser
2034 	function nativeDecode(text) {
2035 		var elm;
2036 
2037 		elm = document.createElement("div");
2038 		elm.innerHTML = text;
2039 
2040 		return elm.textContent || elm.innerText || text;
2041 	};
2042 
2043 	// Build a two way lookup table for the entities
2044 	function buildEntitiesLookup(items, radix) {
2045 		var i, chr, entity, lookup = {};
2046 
2047 		if (items) {
2048 			items = items.split(',');
2049 			radix = radix || 10;
2050 
2051 			// Build entities lookup table
2052 			for (i = 0; i < items.length; i += 2) {
2053 				chr = String.fromCharCode(parseInt(items[i], radix));
2054 
2055 				// Only add non base entities
2056 				if (!baseEntities[chr]) {
2057 					entity = '&' + items[i + 1] + ';';
2058 					lookup[chr] = entity;
2059 					lookup[entity] = chr;
2060 				}
2061 			}
2062 
2063 			return lookup;
2064 		}
2065 	};
2066 
2067 	// Unpack entities lookup where the numbers are in radix 32 to reduce the size
2068 	namedEntities = buildEntitiesLookup(
2069 		'50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
2070 		'5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
2071 		'5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
2072 		'5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
2073 		'68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
2074 		'6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
2075 		'6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
2076 		'75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
2077 		'7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
2078 		'7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
2079 		'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
2080 		'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
2081 		't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
2082 		'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
2083 		'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
2084 		'81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
2085 		'8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
2086 		'8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
2087 		'8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
2088 		'8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
2089 		'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
2090 		'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
2091 		'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
2092 		'80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
2093 		'811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
2094 
2095 	tinymce.html = tinymce.html || {};
2096 
2097 	tinymce.html.Entities = {
2098 		encodeRaw : function(text, attr) {
2099 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2100 				return baseEntities[chr] || chr;
2101 			});
2102 		},
2103 
2104 		encodeAllRaw : function(text) {
2105 			return ('' + text).replace(rawCharsRegExp, function(chr) {
2106 				return baseEntities[chr] || chr;
2107 			});
2108 		},
2109 
2110 		encodeNumeric : function(text, attr) {
2111 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2112 				// Multi byte sequence convert it to a single entity
2113 				if (chr.length > 1)
2114 					return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
2115 
2116 				return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
2117 			});
2118 		},
2119 
2120 		encodeNamed : function(text, attr, entities) {
2121 			entities = entities || namedEntities;
2122 
2123 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2124 				return baseEntities[chr] || entities[chr] || chr;
2125 			});
2126 		},
2127 
2128 		getEncodeFunc : function(name, entities) {
2129 			var Entities = tinymce.html.Entities;
2130 
2131 			entities = buildEntitiesLookup(entities) || namedEntities;
2132 
2133 			function encodeNamedAndNumeric(text, attr) {
2134 				return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2135 					return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
2136 				});
2137 			};
2138 
2139 			function encodeCustomNamed(text, attr) {
2140 				return Entities.encodeNamed(text, attr, entities);
2141 			};
2142 
2143 			// Replace + with , to be compatible with previous TinyMCE versions
2144 			name = tinymce.makeMap(name.replace(/\+/g, ','));
2145 
2146 			// Named and numeric encoder
2147 			if (name.named && name.numeric)
2148 				return encodeNamedAndNumeric;
2149 
2150 			// Named encoder
2151 			if (name.named) {
2152 				// Custom names
2153 				if (entities)
2154 					return encodeCustomNamed;
2155 
2156 				return Entities.encodeNamed;
2157 			}
2158 
2159 			// Numeric
2160 			if (name.numeric)
2161 				return Entities.encodeNumeric;
2162 
2163 			// Raw encoder
2164 			return Entities.encodeRaw;
2165 		},
2166 
2167 		decode : function(text) {
2168 			return text.replace(entityRegExp, function(all, numeric, value) {
2169 				if (numeric) {
2170 					value = parseInt(value, numeric.length === 2 ? 16 : 10);
2171 
2172 					// Support upper UTF
2173 					if (value > 0xFFFF) {
2174 						value -= 0x10000;
2175 
2176 						return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
2177 					} else
2178 						return asciiMap[value] || String.fromCharCode(value);
2179 				}
2180 
2181 				return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
2182 			});
2183 		}
2184 	};
2185 })(tinymce);
2186 
2187 tinymce.html.Styles = function(settings, schema) {
2188 	var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
2189 		urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
2190 		styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
2191 		trimRightRegExp = /\s+$/,
2192 		urlColorRegExp = /rgb/,
2193 		undef, i, encodingLookup = {}, encodingItems;
2194 
2195 	settings = settings || {};
2196 
2197 	encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
2198 	for (i = 0; i < encodingItems.length; i++) {
2199 		encodingLookup[encodingItems[i]] = '\uFEFF' + i;
2200 		encodingLookup['\uFEFF' + i] = encodingItems[i];
2201 	}
2202 
2203 	function toHex(match, r, g, b) {
2204 		function hex(val) {
2205 			val = parseInt(val).toString(16);
2206 
2207 			return val.length > 1 ? val : '0' + val; // 0 -> 00
2208 		};
2209 
2210 		return '#' + hex(r) + hex(g) + hex(b);
2211 	};
2212 
2213 	return {
2214 		toHex : function(color) {
2215 			return color.replace(rgbRegExp, toHex);
2216 		},
2217 
2218 		parse : function(css) {
2219 			var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
2220 
2221 			function compress(prefix, suffix) {
2222 				var top, right, bottom, left;
2223 
2224 				// Get values and check it it needs compressing
2225 				top = styles[prefix + '-top' + suffix];
2226 				if (!top)
2227 					return;
2228 
2229 				right = styles[prefix + '-right' + suffix];
2230 				if (top != right)
2231 					return;
2232 
2233 				bottom = styles[prefix + '-bottom' + suffix];
2234 				if (right != bottom)
2235 					return;
2236 
2237 				left = styles[prefix + '-left' + suffix];
2238 				if (bottom != left)
2239 					return;
2240 
2241 				// Compress
2242 				styles[prefix + suffix] = left;
2243 				delete styles[prefix + '-top' + suffix];
2244 				delete styles[prefix + '-right' + suffix];
2245 				delete styles[prefix + '-bottom' + suffix];
2246 				delete styles[prefix + '-left' + suffix];
2247 			};
2248 
2249 			function canCompress(key) {
2250 				var value = styles[key], i;
2251 
2252 				if (!value || value.indexOf(' ') < 0)
2253 					return;
2254 
2255 				value = value.split(' ');
2256 				i = value.length;
2257 				while (i--) {
2258 					if (value[i] !== value[0])
2259 						return false;
2260 				}
2261 
2262 				styles[key] = value[0];
2263 
2264 				return true;
2265 			};
2266 
2267 			function compress2(target, a, b, c) {
2268 				if (!canCompress(a))
2269 					return;
2270 
2271 				if (!canCompress(b))
2272 					return;
2273 
2274 				if (!canCompress(c))
2275 					return;
2276 
2277 				// Compress
2278 				styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
2279 				delete styles[a];
2280 				delete styles[b];
2281 				delete styles[c];
2282 			};
2283 
2284 			// Encodes the specified string by replacing all \" \' ; : with _<num>
2285 			function encode(str) {
2286 				isEncoded = true;
2287 
2288 				return encodingLookup[str];
2289 			};
2290 
2291 			// Decodes the specified string by replacing all _<num> with it's original value \" \' etc
2292 			// It will also decode the \" \' if keep_slashes is set to fale or omitted
2293 			function decode(str, keep_slashes) {
2294 				if (isEncoded) {
2295 					str = str.replace(/\uFEFF[0-9]/g, function(str) {
2296 						return encodingLookup[str];
2297 					});
2298 				}
2299 
2300 				if (!keep_slashes)
2301 					str = str.replace(/\\([\'\";:])/g, "$1");
2302 
2303 				return str;
2304 			};
2305 
2306 			function processUrl(match, url, url2, url3, str, str2) {
2307 				str = str || str2;
2308 
2309 				if (str) {
2310 					str = decode(str);
2311 
2312 					// Force strings into single quote format
2313 					return "'" + str.replace(/\'/g, "\\'") + "'";
2314 				}
2315 
2316 				url = decode(url || url2 || url3);
2317 
2318 				// Convert the URL to relative/absolute depending on config
2319 				if (urlConverter)
2320 					url = urlConverter.call(urlConverterScope, url, 'style');
2321 
2322 				// Output new URL format
2323 				return "url('" + url.replace(/\'/g, "\\'") + "')";
2324 			};
2325 
2326 			if (css) {
2327 				// Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
2328 				css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
2329 					return str.replace(/[;:]/g, encode);
2330 				});
2331 
2332 				// Parse styles
2333 				while (matches = styleRegExp.exec(css)) {
2334 					name = matches[1].replace(trimRightRegExp, '').toLowerCase();
2335 					value = matches[2].replace(trimRightRegExp, '');
2336 
2337 					if (name && value.length > 0) {
2338 						// Opera will produce 700 instead of bold in their style values
2339 						if (name === 'font-weight' && value === '700')
2340 							value = 'bold';
2341 						else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
2342 							value = value.toLowerCase();		
2343 
2344 						// Convert RGB colors to HEX
2345 						value = value.replace(rgbRegExp, toHex);
2346 
2347 						// Convert URLs and force them into url('value') format
2348 						value = value.replace(urlOrStrRegExp, processUrl);
2349 						styles[name] = isEncoded ? decode(value, true) : value;
2350 					}
2351 
2352 					styleRegExp.lastIndex = matches.index + matches[0].length;
2353 				}
2354 
2355 				// Compress the styles to reduce it's size for example IE will expand styles
2356 				compress("border", "");
2357 				compress("border", "-width");
2358 				compress("border", "-color");
2359 				compress("border", "-style");
2360 				compress("padding", "");
2361 				compress("margin", "");
2362 				compress2('border', 'border-width', 'border-style', 'border-color');
2363 
2364 				// Remove pointless border, IE produces these
2365 				if (styles.border === 'medium none')
2366 					delete styles.border;
2367 			}
2368 
2369 			return styles;
2370 		},
2371 
2372 		serialize : function(styles, element_name) {
2373 			var css = '', name, value;
2374 
2375 			function serializeStyles(name) {
2376 				var styleList, i, l, value;
2377 
2378 				styleList = schema.styles[name];
2379 				if (styleList) {
2380 					for (i = 0, l = styleList.length; i < l; i++) {
2381 						name = styleList[i];
2382 						value = styles[name];
2383 
2384 						if (value !== undef && value.length > 0)
2385 							css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2386 					}
2387 				}
2388 			};
2389 
2390 			// Serialize styles according to schema
2391 			if (element_name && schema && schema.styles) {
2392 				// Serialize global styles and element specific styles
2393 				serializeStyles('*');
2394 				serializeStyles(element_name);
2395 			} else {
2396 				// Output the styles in the order they are inside the object
2397 				for (name in styles) {
2398 					value = styles[name];
2399 
2400 					if (value !== undef && value.length > 0)
2401 						css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2402 				}
2403 			}
2404 
2405 			return css;
2406 		}
2407 	};
2408 };
2409 
2410 (function(tinymce) {
2411 	var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each;
2412 
2413 	function split(str, delim) {
2414 		return str.split(delim || ',');
2415 	};
2416 
2417 	function unpack(lookup, data) {
2418 		var key, elements = {};
2419 
2420 		function replace(value) {
2421 			return value.replace(/[A-Z]+/g, function(key) {
2422 				return replace(lookup[key]);
2423 			});
2424 		};
2425 
2426 		// Unpack lookup
2427 		for (key in lookup) {
2428 			if (lookup.hasOwnProperty(key))
2429 				lookup[key] = replace(lookup[key]);
2430 		}
2431 
2432 		// Unpack and parse data into object map
2433 		replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
2434 			attributes = split(attributes, '|');
2435 
2436 			elements[name] = {
2437 				attributes : makeMap(attributes),
2438 				attributesOrder : attributes,
2439 				children : makeMap(children, '|', {'#comment' : {}})
2440 			}
2441 		});
2442 
2443 		return elements;
2444 	};
2445 
2446 	function getHTML5() {
2447 		var html5 = mapCache.html5;
2448 
2449 		if (!html5) {
2450 			html5 = mapCache.html5 = unpack({
2451 					A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2452 					B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' +
2453 						'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr',
2454 					C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' +
2455 						'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' +
2456 						'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
2457 				}, 'html[A|manifest][body|head]' +
2458 					'head[A][base|command|link|meta|noscript|script|style|title]' +
2459 					'title[A][#]' +
2460 					'base[A|href|target][]' +
2461 					'link[A|href|rel|media|type|sizes][]' +
2462 					'meta[A|http-equiv|name|content|charset][]' +
2463 					'style[A|type|media|scoped][#]' +
2464 					'script[A|charset|type|src|defer|async][#]' +
2465 					'noscript[A][C]' +
2466 					'body[A][C]' +
2467 					'section[A][C]' +
2468 					'nav[A][C]' +
2469 					'article[A][C]' +
2470 					'aside[A][C]' +
2471 					'h1[A][B]' +
2472 					'h2[A][B]' +
2473 					'h3[A][B]' +
2474 					'h4[A][B]' +
2475 					'h5[A][B]' +
2476 					'h6[A][B]' +
2477 					'hgroup[A][h1|h2|h3|h4|h5|h6]' +
2478 					'header[A][C]' +
2479 					'footer[A][C]' +
2480 					'address[A][C]' +
2481 					'p[A][B]' +
2482 					'br[A][]' +
2483 					'pre[A][B]' +
2484 					'dialog[A][dd|dt]' +
2485 					'blockquote[A|cite][C]' +
2486 					'ol[A|start|reversed][li]' +
2487 					'ul[A][li]' +
2488 					'li[A|value][C]' +
2489 					'dl[A][dd|dt]' +
2490 					'dt[A][B]' +
2491 					'dd[A][C]' +
2492 					'a[A|href|target|ping|rel|media|type][B]' +
2493 					'em[A][B]' +
2494 					'strong[A][B]' +
2495 					'small[A][B]' +
2496 					'cite[A][B]' +
2497 					'q[A|cite][B]' +
2498 					'dfn[A][B]' +
2499 					'abbr[A][B]' +
2500 					'code[A][B]' +
2501 					'var[A][B]' +
2502 					'samp[A][B]' +
2503 					'kbd[A][B]' +
2504 					'sub[A][B]' +
2505 					'sup[A][B]' +
2506 					'i[A][B]' +
2507 					'b[A][B]' +
2508 					'mark[A][B]' +
2509 					'progress[A|value|max][B]' +
2510 					'meter[A|value|min|max|low|high|optimum][B]' +
2511 					'time[A|datetime][B]' +
2512 					'ruby[A][B|rt|rp]' +
2513 					'rt[A][B]' +
2514 					'rp[A][B]' +
2515 					'bdo[A][B]' +
2516 					'span[A][B]' +
2517 					'ins[A|cite|datetime][B]' +
2518 					'del[A|cite|datetime][B]' +
2519 					'figure[A][C|legend|figcaption]' +
2520 					'figcaption[A][C]' +
2521 					'img[A|alt|src|height|width|usemap|ismap][]' +
2522 					'iframe[A|name|src|height|width|sandbox|seamless][]' +
2523 					'embed[A|src|height|width|type][]' +
2524 					'object[A|data|type|height|width|usemap|name|form|classid][param]' +
2525 					'param[A|name|value][]' +
2526 					'details[A|open][C|legend]' +
2527 					'command[A|type|label|icon|disabled|checked|radiogroup][]' +
2528 					'menu[A|type|label][C|li]' +
2529 					'legend[A][C|B]' +
2530 					'div[A][C]' +
2531 					'source[A|src|type|media][]' +
2532 					'audio[A|src|autobuffer|autoplay|loop|controls][source]' +
2533 					'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' +
2534 					'hr[A][]' +
2535 					'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' +
2536 					'fieldset[A|disabled|form|name][C|legend]' +
2537 					'label[A|form|for][B]' +
2538 					'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' +
2539 						'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' +
2540 					'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +
2541 					'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' +
2542 					'datalist[A][B|option]' +
2543 					'optgroup[A|disabled|label][option]' +
2544 					'option[A|disabled|selected|label|value][]' +
2545 					'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' +
2546 					'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' +
2547 					'output[A|for|form|name][B]' +
2548 					'canvas[A|width|height][]' +
2549 					'map[A|name][B|C]' +
2550 					'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' +
2551 					'mathml[A][]' +
2552 					'svg[A][]' +
2553 					'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' +
2554 					'caption[A][C]' +
2555 					'colgroup[A|span][col]' +
2556 					'col[A|span][]' +
2557 					'thead[A][tr]' +
2558 					'tfoot[A][tr]' +
2559 					'tbody[A][tr]' +
2560 					'tr[A][th|td]' +
2561 					'th[A|headers|rowspan|colspan|scope][B]' +
2562 					'td[A|headers|rowspan|colspan][C]' +
2563 					'wbr[A][]'
2564 			);
2565 		}
2566 
2567 		return html5;
2568 	};
2569 
2570 	function getHTML4() {
2571 		var html4 = mapCache.html4;
2572 
2573 		if (!html4) {
2574 			// This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
2575 			html4 = mapCache.html4 = unpack({
2576 				Z : 'H|K|N|O|P',
2577 				Y : 'X|form|R|Q',
2578 				ZG : 'E|span|width|align|char|charoff|valign',
2579 				X : 'p|T|div|U|W|isindex|fieldset|table',
2580 				ZF : 'E|align|char|charoff|valign',
2581 				W : 'pre|hr|blockquote|address|center|noframes',
2582 				ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
2583 				ZD : '[E][S]',
2584 				U : 'ul|ol|dl|menu|dir',
2585 				ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
2586 				T : 'h1|h2|h3|h4|h5|h6',
2587 				ZB : 'X|S|Q',
2588 				S : 'R|P',
2589 				ZA : 'a|G|J|M|O|P',
2590 				R : 'a|H|K|N|O',
2591 				Q : 'noscript|P',
2592 				P : 'ins|del|script',
2593 				O : 'input|select|textarea|label|button',
2594 				N : 'M|L',
2595 				M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
2596 				L : 'sub|sup',
2597 				K : 'J|I',
2598 				J : 'tt|i|b|u|s|strike',
2599 				I : 'big|small|font|basefont',
2600 				H : 'G|F',
2601 				G : 'br|span|bdo',
2602 				F : 'object|applet|img|map|iframe',
2603 				E : 'A|B|C',
2604 				D : 'accesskey|tabindex|onfocus|onblur',
2605 				C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2606 				B : 'lang|xml:lang|dir',
2607 				A : 'id|class|style|title'
2608 			}, 'script[id|charset|type|language|src|defer|xml:space][]' + 
2609 				'style[B|id|type|media|title|xml:space][]' + 
2610 				'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 
2611 				'param[id|name|value|valuetype|type][]' + 
2612 				'p[E|align][#|S]' + 
2613 				'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 
2614 				'br[A|clear][]' + 
2615 				'span[E][#|S]' + 
2616 				'bdo[A|C|B][#|S]' + 
2617 				'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 
2618 				'h1[E|align][#|S]' + 
2619 				'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 
2620 				'map[B|C|A|name][X|form|Q|area]' + 
2621 				'h2[E|align][#|S]' + 
2622 				'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 
2623 				'h3[E|align][#|S]' + 
2624 				'tt[E][#|S]' + 
2625 				'i[E][#|S]' + 
2626 				'b[E][#|S]' + 
2627 				'u[E][#|S]' + 
2628 				's[E][#|S]' + 
2629 				'strike[E][#|S]' + 
2630 				'big[E][#|S]' + 
2631 				'small[E][#|S]' + 
2632 				'font[A|B|size|color|face][#|S]' + 
2633 				'basefont[id|size|color|face][]' + 
2634 				'em[E][#|S]' + 
2635 				'strong[E][#|S]' + 
2636 				'dfn[E][#|S]' + 
2637 				'code[E][#|S]' + 
2638 				'q[E|cite][#|S]' + 
2639 				'samp[E][#|S]' + 
2640 				'kbd[E][#|S]' + 
2641 				'var[E][#|S]' + 
2642 				'cite[E][#|S]' + 
2643 				'abbr[E][#|S]' + 
2644 				'acronym[E][#|S]' + 
2645 				'sub[E][#|S]' + 
2646 				'sup[E][#|S]' + 
2647 				'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 
2648 				'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 
2649 				'optgroup[E|disabled|label][option]' + 
2650 				'option[E|selected|disabled|label|value][]' + 
2651 				'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 
2652 				'label[E|for|accesskey|onfocus|onblur][#|S]' + 
2653 				'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 
2654 				'h4[E|align][#|S]' + 
2655 				'ins[E|cite|datetime][#|Y]' + 
2656 				'h5[E|align][#|S]' + 
2657 				'del[E|cite|datetime][#|Y]' + 
2658 				'h6[E|align][#|S]' + 
2659 				'div[E|align][#|Y]' + 
2660 				'ul[E|type|compact][li]' + 
2661 				'li[E|type|value][#|Y]' + 
2662 				'ol[E|type|compact|start][li]' + 
2663 				'dl[E|compact][dt|dd]' + 
2664 				'dt[E][#|S]' + 
2665 				'dd[E][#|Y]' + 
2666 				'menu[E|compact][li]' + 
2667 				'dir[E|compact][li]' + 
2668 				'pre[E|width|xml:space][#|ZA]' + 
2669 				'hr[E|align|noshade|size|width][]' + 
2670 				'blockquote[E|cite][#|Y]' + 
2671 				'address[E][#|S|p]' + 
2672 				'center[E][#|Y]' + 
2673 				'noframes[E][#|Y]' + 
2674 				'isindex[A|B|prompt][]' + 
2675 				'fieldset[E][#|legend|Y]' + 
2676 				'legend[E|accesskey|align][#|S]' + 
2677 				'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 
2678 				'caption[E|align][#|S]' + 
2679 				'col[ZG][]' + 
2680 				'colgroup[ZG][col]' + 
2681 				'thead[ZF][tr]' + 
2682 				'tr[ZF|bgcolor][th|td]' + 
2683 				'th[E|ZE][#|Y]' + 
2684 				'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 
2685 				'noscript[E][#|Y]' + 
2686 				'td[E|ZE][#|Y]' + 
2687 				'tfoot[ZF][tr]' + 
2688 				'tbody[ZF][tr]' + 
2689 				'area[E|D|shape|coords|href|nohref|alt|target][]' + 
2690 				'base[id|href|target][]' + 
2691 				'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
2692 			);
2693 		}
2694 
2695 		return html4;
2696 	};
2697 
2698 	tinymce.html.Schema = function(settings) {
2699 		var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
2700 		var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {};
2701 
2702 		// Creates an lookup table map object for the specified option or the default value
2703 		function createLookupTable(option, default_value, extend) {
2704 			var value = settings[option];
2705 
2706 			if (!value) {
2707 				// Get cached default map or make it if needed
2708 				value = mapCache[option];
2709 
2710 				if (!value) {
2711 					value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
2712 					value = tinymce.extend(value, extend);
2713 
2714 					mapCache[option] = value;
2715 				}
2716 			} else {
2717 				// Create custom map
2718 				value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));
2719 			}
2720 
2721 			return value;
2722 		};
2723 
2724 		settings = settings || {};
2725 		schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4();
2726 
2727 		// Allow all elements and attributes if verify_html is set to false
2728 		if (settings.verify_html === false)
2729 			settings.valid_elements = '*[*]';
2730 
2731 		// Build styles list
2732 		if (settings.valid_styles) {
2733 			validStyles = {};
2734 
2735 			// Convert styles into a rule list
2736 			each(settings.valid_styles, function(value, key) {
2737 				validStyles[key] = tinymce.explode(value);
2738 			});
2739 		}
2740 
2741 		// Setup map objects
2742 		whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea');
2743 		selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
2744 		shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr');
2745 		boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls');
2746 		nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap);
2747 		blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' + 
2748 						'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' + 
2749 						'noscript menu isindex samp header footer article section hgroup aside nav figure option datalist select optgroup');
2750 
2751 		// Converts a wildcard expression string to a regexp for example *a will become /.*a/.
2752 		function patternToRegExp(str) {
2753 			return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
2754 		};
2755 
2756 		// Parses the specified valid_elements string and adds to the current rules
2757 		// This function is a bit hard to read since it's heavily optimized for speed
2758 		function addValidElements(valid_elements) {
2759 			var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
2760 				prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
2761 				elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
2762 				attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
2763 				hasPatternsRegExp = /[*?+]/;
2764 
2765 			if (valid_elements) {
2766 				// Split valid elements into an array with rules
2767 				valid_elements = split(valid_elements);
2768 
2769 				if (elements['@']) {
2770 					globalAttributes = elements['@'].attributes;
2771 					globalAttributesOrder = elements['@'].attributesOrder;
2772 				}
2773 
2774 				// Loop all rules
2775 				for (ei = 0, el = valid_elements.length; ei < el; ei++) {
2776 					// Parse element rule
2777 					matches = elementRuleRegExp.exec(valid_elements[ei]);
2778 					if (matches) {
2779 						// Setup local names for matches
2780 						prefix = matches[1];
2781 						elementName = matches[2];
2782 						outputName = matches[3];
2783 						attrData = matches[4];
2784 
2785 						// Create new attributes and attributesOrder
2786 						attributes = {};
2787 						attributesOrder = [];
2788 
2789 						// Create the new element
2790 						element = {
2791 							attributes : attributes,
2792 							attributesOrder : attributesOrder
2793 						};
2794 
2795 						// Padd empty elements prefix
2796 						if (prefix === '#')
2797 							element.paddEmpty = true;
2798 
2799 						// Remove empty elements prefix
2800 						if (prefix === '-')
2801 							element.removeEmpty = true;
2802 
2803 						// Copy attributes from global rule into current rule
2804 						if (globalAttributes) {
2805 							for (key in globalAttributes)
2806 								attributes[key] = globalAttributes[key];
2807 
2808 							attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
2809 						}
2810 
2811 						// Attributes defined
2812 						if (attrData) {
2813 							attrData = split(attrData, '|');
2814 							for (ai = 0, al = attrData.length; ai < al; ai++) {
2815 								matches = attrRuleRegExp.exec(attrData[ai]);
2816 								if (matches) {
2817 									attr = {};
2818 									attrType = matches[1];
2819 									attrName = matches[2].replace(/::/g, ':');
2820 									prefix = matches[3];
2821 									value = matches[4];
2822 
2823 									// Required
2824 									if (attrType === '!') {
2825 										element.attributesRequired = element.attributesRequired || [];
2826 										element.attributesRequired.push(attrName);
2827 										attr.required = true;
2828 									}
2829 
2830 									// Denied from global
2831 									if (attrType === '-') {
2832 										delete attributes[attrName];
2833 										attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
2834 										continue;
2835 									}
2836 
2837 									// Default value
2838 									if (prefix) {
2839 										// Default value
2840 										if (prefix === '=') {
2841 											element.attributesDefault = element.attributesDefault || [];
2842 											element.attributesDefault.push({name: attrName, value: value});
2843 											attr.defaultValue = value;
2844 										}
2845 
2846 										// Forced value
2847 										if (prefix === ':') {
2848 											element.attributesForced = element.attributesForced || [];
2849 											element.attributesForced.push({name: attrName, value: value});
2850 											attr.forcedValue = value;
2851 										}
2852 
2853 										// Required values
2854 										if (prefix === '<')
2855 											attr.validValues = makeMap(value, '?');
2856 									}
2857 
2858 									// Check for attribute patterns
2859 									if (hasPatternsRegExp.test(attrName)) {
2860 										element.attributePatterns = element.attributePatterns || [];
2861 										attr.pattern = patternToRegExp(attrName);
2862 										element.attributePatterns.push(attr);
2863 									} else {
2864 										// Add attribute to order list if it doesn't already exist
2865 										if (!attributes[attrName])
2866 											attributesOrder.push(attrName);
2867 
2868 										attributes[attrName] = attr;
2869 									}
2870 								}
2871 							}
2872 						}
2873 
2874 						// Global rule, store away these for later usage
2875 						if (!globalAttributes && elementName == '@') {
2876 							globalAttributes = attributes;
2877 							globalAttributesOrder = attributesOrder;
2878 						}
2879 
2880 						// Handle substitute elements such as b/strong
2881 						if (outputName) {
2882 							element.outputName = elementName;
2883 							elements[outputName] = element;
2884 						}
2885 
2886 						// Add pattern or exact element
2887 						if (hasPatternsRegExp.test(elementName)) {
2888 							element.pattern = patternToRegExp(elementName);
2889 							patternElements.push(element);
2890 						} else
2891 							elements[elementName] = element;
2892 					}
2893 				}
2894 			}
2895 		};
2896 
2897 		function setValidElements(valid_elements) {
2898 			elements = {};
2899 			patternElements = [];
2900 
2901 			addValidElements(valid_elements);
2902 
2903 			each(schemaItems, function(element, name) {
2904 				children[name] = element.children;
2905 			});
2906 		};
2907 
2908 		// Adds custom non HTML elements to the schema
2909 		function addCustomElements(custom_elements) {
2910 			var customElementRegExp = /^(~)?(.+)$/;
2911 
2912 			if (custom_elements) {
2913 				each(split(custom_elements), function(rule) {
2914 					var matches = customElementRegExp.exec(rule),
2915 						inline = matches[1] === '~',
2916 						cloneName = inline ? 'span' : 'div',
2917 						name = matches[2];
2918 
2919 					children[name] = children[cloneName];
2920 					customElementsMap[name] = cloneName;
2921 
2922 					// If it's not marked as inline then add it to valid block elements
2923 					if (!inline)
2924 						blockElementsMap[name] = {};
2925 
2926 					// Add custom elements at span/div positions
2927 					each(children, function(element, child) {
2928 						if (element[cloneName])
2929 							element[name] = element[cloneName];
2930 					});
2931 				});
2932 			}
2933 		};
2934 
2935 		// Adds valid children to the schema object
2936 		function addValidChildren(valid_children) {
2937 			var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
2938 
2939 			if (valid_children) {
2940 				each(split(valid_children), function(rule) {
2941 					var matches = childRuleRegExp.exec(rule), parent, prefix;
2942 
2943 					if (matches) {
2944 						prefix = matches[1];
2945 
2946 						// Add/remove items from default
2947 						if (prefix)
2948 							parent = children[matches[2]];
2949 						else
2950 							parent = children[matches[2]] = {'#comment' : {}};
2951 
2952 						parent = children[matches[2]];
2953 
2954 						each(split(matches[3], '|'), function(child) {
2955 							if (prefix === '-')
2956 								delete parent[child];
2957 							else
2958 								parent[child] = {};
2959 						});
2960 					}
2961 				});
2962 			}
2963 		};
2964 
2965 		function getElementRule(name) {
2966 			var element = elements[name], i;
2967 
2968 			// Exact match found
2969 			if (element)
2970 				return element;
2971 
2972 			// No exact match then try the patterns
2973 			i = patternElements.length;
2974 			while (i--) {
2975 				element = patternElements[i];
2976 
2977 				if (element.pattern.test(name))
2978 					return element;
2979 			}
2980 		};
2981 
2982 		if (!settings.valid_elements) {
2983 			// No valid elements defined then clone the elements from the schema spec
2984 			each(schemaItems, function(element, name) {
2985 				elements[name] = {
2986 					attributes : element.attributes,
2987 					attributesOrder : element.attributesOrder
2988 				};
2989 
2990 				children[name] = element.children;
2991 			});
2992 
2993 			// Switch these on HTML4
2994 			if (settings.schema != "html5") {
2995 				each(split('strong/b,em/i'), function(item) {
2996 					item = split(item, '/');
2997 					elements[item[1]].outputName = item[0];
2998 				});
2999 			}
3000 
3001 			// Add default alt attribute for images
3002 			elements.img.attributesDefault = [{name: 'alt', value: ''}];
3003 
3004 			// Remove these if they are empty by default
3005 			each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) {
3006 				if (elements[name]) {
3007 					elements[name].removeEmpty = true;
3008 				}
3009 			});
3010 
3011 			// Padd these by default
3012 			each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
3013 				elements[name].paddEmpty = true;
3014 			});
3015 		} else
3016 			setValidElements(settings.valid_elements);
3017 
3018 		addCustomElements(settings.custom_elements);
3019 		addValidChildren(settings.valid_children);
3020 		addValidElements(settings.extended_valid_elements);
3021 
3022 		// Todo: Remove this when we fix list handling to be valid
3023 		addValidChildren('+ol[ul|ol],+ul[ul|ol]');
3024 
3025 		// Delete invalid elements
3026 		if (settings.invalid_elements) {
3027 			tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
3028 				if (elements[item])
3029 					delete elements[item];
3030 			});
3031 		}
3032 
3033 		// If the user didn't allow span only allow internal spans
3034 		if (!getElementRule('span'))
3035 			addValidElements('span[!data-mce-type|*]');
3036 
3037 		self.children = children;
3038 
3039 		self.styles = validStyles;
3040 
3041 		self.getBoolAttrs = function() {
3042 			return boolAttrMap;
3043 		};
3044 
3045 		self.getBlockElements = function() {
3046 			return blockElementsMap;
3047 		};
3048 
3049 		self.getShortEndedElements = function() {
3050 			return shortEndedElementsMap;
3051 		};
3052 
3053 		self.getSelfClosingElements = function() {
3054 			return selfClosingElementsMap;
3055 		};
3056 
3057 		self.getNonEmptyElements = function() {
3058 			return nonEmptyElementsMap;
3059 		};
3060 
3061 		self.getWhiteSpaceElements = function() {
3062 			return whiteSpaceElementsMap;
3063 		};
3064 
3065 		self.isValidChild = function(name, child) {
3066 			var parent = children[name];
3067 
3068 			return !!(parent && parent[child]);
3069 		};
3070 
3071 		self.isValid = function(name, attr) {
3072 			var attrPatterns, i, rule = getElementRule(name);
3073 
3074 			// Check if it's a valid element
3075 			if (rule) {
3076 				if (attr) {
3077 					// Check if attribute name exists
3078 					if (rule.attributes[attr]) {
3079 						return true;
3080 					}
3081 
3082 					// Check if attribute matches a regexp pattern
3083 					attrPatterns = rule.attributePatterns;
3084 					if (attrPatterns) {
3085 						i = attrPatterns.length;
3086 						while (i--) {
3087 							if (attrPatterns[i].pattern.test(name)) {
3088 								return true;
3089 							}
3090 						}
3091 					}
3092 				} else {
3093 					return true;
3094 				}
3095 			}
3096 
3097 			// No match
3098 			return false;
3099 		};
3100 		
3101 		self.getElementRule = getElementRule;
3102 
3103 		self.getCustomElements = function() {
3104 			return customElementsMap;
3105 		};
3106 
3107 		self.addValidElements = addValidElements;
3108 
3109 		self.setValidElements = setValidElements;
3110 
3111 		self.addCustomElements = addCustomElements;
3112 
3113 		self.addValidChildren = addValidChildren;
3114 	};
3115 })(tinymce);
3116 
3117 (function(tinymce) {
3118 	tinymce.html.SaxParser = function(settings, schema) {
3119 		var self = this, noop = function() {};
3120 
3121 		settings = settings || {};
3122 		self.schema = schema = schema || new tinymce.html.Schema();
3123 
3124 		if (settings.fix_self_closing !== false)
3125 			settings.fix_self_closing = true;
3126 
3127 		// Add handler functions from settings and setup default handlers
3128 		tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
3129 			if (name)
3130 				self[name] = settings[name] || noop;
3131 		});
3132 
3133 		self.parse = function(html) {
3134 			var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
3135 				shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,
3136 				validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
3137 				tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;
3138 
3139 			function processEndTag(name) {
3140 				var pos, i;
3141 
3142 				// Find position of parent of the same type
3143 				pos = stack.length;
3144 				while (pos--) {
3145 					if (stack[pos].name === name)
3146 						break;						
3147 				}
3148 
3149 				// Found parent
3150 				if (pos >= 0) {
3151 					// Close all the open elements
3152 					for (i = stack.length - 1; i >= pos; i--) {
3153 						name = stack[i];
3154 
3155 						if (name.valid)
3156 							self.end(name.name);
3157 					}
3158 
3159 					// Remove the open elements from the stack
3160 					stack.length = pos;
3161 				}
3162 			};
3163 
3164 			function parseAttribute(match, name, value, val2, val3) {
3165 				var attrRule, i;
3166 
3167 				name = name.toLowerCase();
3168 				value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
3169 
3170 				// Validate name and value
3171 				if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
3172 					attrRule = validAttributesMap[name];
3173 
3174 					// Find rule by pattern matching
3175 					if (!attrRule && validAttributePatterns) {
3176 						i = validAttributePatterns.length;
3177 						while (i--) {
3178 							attrRule = validAttributePatterns[i];
3179 							if (attrRule.pattern.test(name))
3180 								break;
3181 						}
3182 
3183 						// No rule matched
3184 						if (i === -1)
3185 							attrRule = null;
3186 					}
3187 
3188 					// No attribute rule found
3189 					if (!attrRule)
3190 						return;
3191 
3192 					// Validate value
3193 					if (attrRule.validValues && !(value in attrRule.validValues))
3194 						return;
3195 				}
3196 
3197 				// Add attribute to list and map
3198 				attrList.map[name] = value;
3199 				attrList.push({
3200 					name: name,
3201 					value: value
3202 				});
3203 			};
3204 
3205 			// Precompile RegExps and map objects
3206 			tokenRegExp = new RegExp('<(?:' +
3207 				'(?:!--([\\w\\W]*?)-->)|' + // Comment
3208 				'(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
3209 				'(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
3210 				'(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
3211 				'(?:\\/([^>]+)>)|' + // End element
3212 				'(?:([A-Za-z0-9\\-\\:]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
3213 			')', 'g');
3214 
3215 			attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
3216 			specialElements = {
3217 				'script' : /<\/script[^>]*>/gi,
3218 				'style' : /<\/style[^>]*>/gi,
3219 				'noscript' : /<\/noscript[^>]*>/gi
3220 			};
3221 
3222 			// Setup lookup tables for empty elements and boolean attributes
3223 			shortEndedElements = schema.getShortEndedElements();
3224 			selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
3225 			fillAttrsMap = schema.getBoolAttrs();
3226 			validate = settings.validate;
3227 			removeInternalElements = settings.remove_internals;
3228 			fixSelfClosing = settings.fix_self_closing;
3229 			isIE = tinymce.isIE;
3230 			invalidPrefixRegExp = /^:/;
3231 
3232 			while (matches = tokenRegExp.exec(html)) {
3233 				// Text
3234 				if (index < matches.index)
3235 					self.text(decode(html.substr(index, matches.index - index)));
3236 
3237 				if (value = matches[6]) { // End element
3238 					value = value.toLowerCase();
3239 
3240 					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3241 					if (isIE && invalidPrefixRegExp.test(value))
3242 						value = value.substr(1);
3243 
3244 					processEndTag(value);
3245 				} else if (value = matches[7]) { // Start element
3246 					value = value.toLowerCase();
3247 
3248 					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3249 					if (isIE && invalidPrefixRegExp.test(value))
3250 						value = value.substr(1);
3251 
3252 					isShortEnded = value in shortEndedElements;
3253 
3254 					// Is self closing tag for example an <li> after an open <li>
3255 					if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
3256 						processEndTag(value);
3257 
3258 					// Validate element
3259 					if (!validate || (elementRule = schema.getElementRule(value))) {
3260 						isValidElement = true;
3261 
3262 						// Grab attributes map and patters when validation is enabled
3263 						if (validate) {
3264 							validAttributesMap = elementRule.attributes;
3265 							validAttributePatterns = elementRule.attributePatterns;
3266 						}
3267 
3268 						// Parse attributes
3269 						if (attribsValue = matches[8]) {
3270 							isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
3271 
3272 							// If the element has internal attributes then remove it if we are told to do so
3273 							if (isInternalElement && removeInternalElements)
3274 								isValidElement = false;
3275 
3276 							attrList = [];
3277 							attrList.map = {};
3278 
3279 							attribsValue.replace(attrRegExp, parseAttribute);
3280 						} else {
3281 							attrList = [];
3282 							attrList.map = {};
3283 						}
3284 
3285 						// Process attributes if validation is enabled
3286 						if (validate && !isInternalElement) {
3287 							attributesRequired = elementRule.attributesRequired;
3288 							attributesDefault = elementRule.attributesDefault;
3289 							attributesForced = elementRule.attributesForced;
3290 
3291 							// Handle forced attributes
3292 							if (attributesForced) {
3293 								i = attributesForced.length;
3294 								while (i--) {
3295 									attr = attributesForced[i];
3296 									name = attr.name;
3297 									attrValue = attr.value;
3298 
3299 									if (attrValue === '{$uid}')
3300 										attrValue = 'mce_' + idCount++;
3301 
3302 									attrList.map[name] = attrValue;
3303 									attrList.push({name: name, value: attrValue});
3304 								}
3305 							}
3306 
3307 							// Handle default attributes
3308 							if (attributesDefault) {
3309 								i = attributesDefault.length;
3310 								while (i--) {
3311 									attr = attributesDefault[i];
3312 									name = attr.name;
3313 
3314 									if (!(name in attrList.map)) {
3315 										attrValue = attr.value;
3316 
3317 										if (attrValue === '{$uid}')
3318 											attrValue = 'mce_' + idCount++;
3319 
3320 										attrList.map[name] = attrValue;
3321 										attrList.push({name: name, value: attrValue});
3322 									}
3323 								}
3324 							}
3325 
3326 							// Handle required attributes
3327 							if (attributesRequired) {
3328 								i = attributesRequired.length;
3329 								while (i--) {
3330 									if (attributesRequired[i] in attrList.map)
3331 										break;
3332 								}
3333 
3334 								// None of the required attributes where found
3335 								if (i === -1)
3336 									isValidElement = false;
3337 							}
3338 
3339 							// Invalidate element if it's marked as bogus
3340 							if (attrList.map['data-mce-bogus'])
3341 								isValidElement = false;
3342 						}
3343 
3344 						if (isValidElement)
3345 							self.start(value, attrList, isShortEnded);
3346 					} else
3347 						isValidElement = false;
3348 
3349 					// Treat script, noscript and style a bit different since they may include code that looks like elements
3350 					if (endRegExp = specialElements[value]) {
3351 						endRegExp.lastIndex = index = matches.index + matches[0].length;
3352 
3353 						if (matches = endRegExp.exec(html)) {
3354 							if (isValidElement)
3355 								text = html.substr(index, matches.index - index);
3356 
3357 							index = matches.index + matches[0].length;
3358 						} else {
3359 							text = html.substr(index);
3360 							index = html.length;
3361 						}
3362 
3363 						if (isValidElement && text.length > 0)
3364 							self.text(text, true);
3365 
3366 						if (isValidElement)
3367 							self.end(value);
3368 
3369 						tokenRegExp.lastIndex = index;
3370 						continue;
3371 					}
3372 
3373 					// Push value on to stack
3374 					if (!isShortEnded) {
3375 						if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
3376 							stack.push({name: value, valid: isValidElement});
3377 						else if (isValidElement)
3378 							self.end(value);
3379 					}
3380 				} else if (value = matches[1]) { // Comment
3381 					self.comment(value);
3382 				} else if (value = matches[2]) { // CDATA
3383 					self.cdata(value);
3384 				} else if (value = matches[3]) { // DOCTYPE
3385 					self.doctype(value);
3386 				} else if (value = matches[4]) { // PI
3387 					self.pi(value, matches[5]);
3388 				}
3389 
3390 				index = matches.index + matches[0].length;
3391 			}
3392 
3393 			// Text
3394 			if (index < html.length)
3395 				self.text(decode(html.substr(index)));
3396 
3397 			// Close any open elements
3398 			for (i = stack.length - 1; i >= 0; i--) {
3399 				value = stack[i];
3400 
3401 				if (value.valid)
3402 					self.end(value.name);
3403 			}
3404 		};
3405 	}
3406 })(tinymce);
3407 
3408 (function(tinymce) {
3409 	var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
3410 		'#text' : 3,
3411 		'#comment' : 8,
3412 		'#cdata' : 4,
3413 		'#pi' : 7,
3414 		'#doctype' : 10,
3415 		'#document-fragment' : 11
3416 	};
3417 
3418 	// Walks the tree left/right
3419 	function walk(node, root_node, prev) {
3420 		var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
3421 
3422 		// Walk into nodes if it has a start
3423 		if (node[startName])
3424 			return node[startName];
3425 
3426 		// Return the sibling if it has one
3427 		if (node !== root_node) {
3428 			sibling = node[siblingName];
3429 
3430 			if (sibling)
3431 				return sibling;
3432 
3433 			// Walk up the parents to look for siblings
3434 			for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
3435 				sibling = parent[siblingName];
3436 
3437 				if (sibling)
3438 					return sibling;
3439 			}
3440 		}
3441 	};
3442 
3443 	function Node(name, type) {
3444 		this.name = name;
3445 		this.type = type;
3446 
3447 		if (type === 1) {
3448 			this.attributes = [];
3449 			this.attributes.map = {};
3450 		}
3451 	}
3452 
3453 	tinymce.extend(Node.prototype, {
3454 		replace : function(node) {
3455 			var self = this;
3456 
3457 			if (node.parent)
3458 				node.remove();
3459 
3460 			self.insert(node, self);
3461 			self.remove();
3462 
3463 			return self;
3464 		},
3465 
3466 		attr : function(name, value) {
3467 			var self = this, attrs, i, undef;
3468 
3469 			if (typeof name !== "string") {
3470 				for (i in name)
3471 					self.attr(i, name[i]);
3472 
3473 				return self;
3474 			}
3475 
3476 			if (attrs = self.attributes) {
3477 				if (value !== undef) {
3478 					// Remove attribute
3479 					if (value === null) {
3480 						if (name in attrs.map) {
3481 							delete attrs.map[name];
3482 
3483 							i = attrs.length;
3484 							while (i--) {
3485 								if (attrs[i].name === name) {
3486 									attrs = attrs.splice(i, 1);
3487 									return self;
3488 								}
3489 							}
3490 						}
3491 
3492 						return self;
3493 					}
3494 
3495 					// Set attribute
3496 					if (name in attrs.map) {
3497 						// Set attribute
3498 						i = attrs.length;
3499 						while (i--) {
3500 							if (attrs[i].name === name) {
3501 								attrs[i].value = value;
3502 								break;
3503 							}
3504 						}
3505 					} else
3506 						attrs.push({name: name, value: value});
3507 
3508 					attrs.map[name] = value;
3509 
3510 					return self;
3511 				} else {
3512 					return attrs.map[name];
3513 				}
3514 			}
3515 		},
3516 
3517 		clone : function() {
3518 			var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
3519 
3520 			// Clone element attributes
3521 			if (selfAttrs = self.attributes) {
3522 				cloneAttrs = [];
3523 				cloneAttrs.map = {};
3524 
3525 				for (i = 0, l = selfAttrs.length; i < l; i++) {
3526 					selfAttr = selfAttrs[i];
3527 
3528 					// Clone everything except id
3529 					if (selfAttr.name !== 'id') {
3530 						cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
3531 						cloneAttrs.map[selfAttr.name] = selfAttr.value;
3532 					}
3533 				}
3534 
3535 				clone.attributes = cloneAttrs;
3536 			}
3537 
3538 			clone.value = self.value;
3539 			clone.shortEnded = self.shortEnded;
3540 
3541 			return clone;
3542 		},
3543 
3544 		wrap : function(wrapper) {
3545 			var self = this;
3546 
3547 			self.parent.insert(wrapper, self);
3548 			wrapper.append(self);
3549 
3550 			return self;
3551 		},
3552 
3553 		unwrap : function() {
3554 			var self = this, node, next;
3555 
3556 			for (node = self.firstChild; node; ) {
3557 				next = node.next;
3558 				self.insert(node, self, true);
3559 				node = next;
3560 			}
3561 
3562 			self.remove();
3563 		},
3564 
3565 		remove : function() {
3566 			var self = this, parent = self.parent, next = self.next, prev = self.prev;
3567 
3568 			if (parent) {
3569 				if (parent.firstChild === self) {
3570 					parent.firstChild = next;
3571 
3572 					if (next)
3573 						next.prev = null;
3574 				} else {
3575 					prev.next = next;
3576 				}
3577 
3578 				if (parent.lastChild === self) {
3579 					parent.lastChild = prev;
3580 
3581 					if (prev)
3582 						prev.next = null;
3583 				} else {
3584 					next.prev = prev;
3585 				}
3586 
3587 				self.parent = self.next = self.prev = null;
3588 			}
3589 
3590 			return self;
3591 		},
3592 
3593 		append : function(node) {
3594 			var self = this, last;
3595 
3596 			if (node.parent)
3597 				node.remove();
3598 
3599 			last = self.lastChild;
3600 			if (last) {
3601 				last.next = node;
3602 				node.prev = last;
3603 				self.lastChild = node;
3604 			} else
3605 				self.lastChild = self.firstChild = node;
3606 
3607 			node.parent = self;
3608 
3609 			return node;
3610 		},
3611 
3612 		insert : function(node, ref_node, before) {
3613 			var parent;
3614 
3615 			if (node.parent)
3616 				node.remove();
3617 
3618 			parent = ref_node.parent || this;
3619 
3620 			if (before) {
3621 				if (ref_node === parent.firstChild)
3622 					parent.firstChild = node;
3623 				else
3624 					ref_node.prev.next = node;
3625 
3626 				node.prev = ref_node.prev;
3627 				node.next = ref_node;
3628 				ref_node.prev = node;
3629 			} else {
3630 				if (ref_node === parent.lastChild)
3631 					parent.lastChild = node;
3632 				else
3633 					ref_node.next.prev = node;
3634 
3635 				node.next = ref_node.next;
3636 				node.prev = ref_node;
3637 				ref_node.next = node;
3638 			}
3639 
3640 			node.parent = parent;
3641 
3642 			return node;
3643 		},
3644 
3645 		getAll : function(name) {
3646 			var self = this, node, collection = [];
3647 
3648 			for (node = self.firstChild; node; node = walk(node, self)) {
3649 				if (node.name === name)
3650 					collection.push(node);
3651 			}
3652 
3653 			return collection;
3654 		},
3655 
3656 		empty : function() {
3657 			var self = this, nodes, i, node;
3658 
3659 			// Remove all children
3660 			if (self.firstChild) {
3661 				nodes = [];
3662 
3663 				// Collect the children
3664 				for (node = self.firstChild; node; node = walk(node, self))
3665 					nodes.push(node);
3666 
3667 				// Remove the children
3668 				i = nodes.length;
3669 				while (i--) {
3670 					node = nodes[i];
3671 					node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
3672 				}
3673 			}
3674 
3675 			self.firstChild = self.lastChild = null;
3676 
3677 			return self;
3678 		},
3679 
3680 		isEmpty : function(elements) {
3681 			var self = this, node = self.firstChild, i, name;
3682 
3683 			if (node) {
3684 				do {
3685 					if (node.type === 1) {
3686 						// Ignore bogus elements
3687 						if (node.attributes.map['data-mce-bogus'])
3688 							continue;
3689 
3690 						// Keep empty elements like <img />
3691 						if (elements[node.name])
3692 							return false;
3693 
3694 						// Keep elements with data attributes or name attribute like <a name="1"></a>
3695 						i = node.attributes.length;
3696 						while (i--) {
3697 							name = node.attributes[i].name;
3698 							if (name === "name" || name.indexOf('data-') === 0)
3699 								return false;
3700 						}
3701 					}
3702 
3703 					// Keep comments
3704 					if (node.type === 8)
3705 						return false;
3706 					
3707 					// Keep non whitespace text nodes
3708 					if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
3709 						return false;
3710 				} while (node = walk(node, self));
3711 			}
3712 
3713 			return true;
3714 		},
3715 
3716 		walk : function(prev) {
3717 			return walk(this, null, prev);
3718 		}
3719 	});
3720 
3721 	tinymce.extend(Node, {
3722 		create : function(name, attrs) {
3723 			var node, attrName;
3724 
3725 			// Create node
3726 			node = new Node(name, typeLookup[name] || 1);
3727 
3728 			// Add attributes if needed
3729 			if (attrs) {
3730 				for (attrName in attrs)
3731 					node.attr(attrName, attrs[attrName]);
3732 			}
3733 
3734 			return node;
3735 		}
3736 	});
3737 
3738 	tinymce.html.Node = Node;
3739 })(tinymce);
3740 
3741 (function(tinymce) {
3742 	var Node = tinymce.html.Node;
3743 
3744 	tinymce.html.DomParser = function(settings, schema) {
3745 		var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
3746 
3747 		settings = settings || {};
3748 		settings.validate = "validate" in settings ? settings.validate : true;
3749 		settings.root_name = settings.root_name || 'body';
3750 		self.schema = schema = schema || new tinymce.html.Schema();
3751 
3752 		function fixInvalidChildren(nodes) {
3753 			var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
3754 				childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
3755 
3756 			nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
3757 			nonEmptyElements = schema.getNonEmptyElements();
3758 
3759 			for (ni = 0; ni < nodes.length; ni++) {
3760 				node = nodes[ni];
3761 
3762 				// Already removed
3763 				if (!node.parent)
3764 					continue;
3765 
3766 				// Get list of all parent nodes until we find a valid parent to stick the child into
3767 				parents = [node];
3768 				for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
3769 					parents.push(parent);
3770 
3771 				// Found a suitable parent
3772 				if (parent && parents.length > 1) {
3773 					// Reverse the array since it makes looping easier
3774 					parents.reverse();
3775 
3776 					// Clone the related parent and insert that after the moved node
3777 					newParent = currentNode = self.filterNode(parents[0].clone());
3778 
3779 					// Start cloning and moving children on the left side of the target node
3780 					for (i = 0; i < parents.length - 1; i++) {
3781 						if (schema.isValidChild(currentNode.name, parents[i].name)) {
3782 							tempNode = self.filterNode(parents[i].clone());
3783 							currentNode.append(tempNode);
3784 						} else
3785 							tempNode = currentNode;
3786 
3787 						for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
3788 							nextNode = childNode.next;
3789 							tempNode.append(childNode);
3790 							childNode = nextNode;
3791 						}
3792 
3793 						currentNode = tempNode;
3794 					}
3795 
3796 					if (!newParent.isEmpty(nonEmptyElements)) {
3797 						parent.insert(newParent, parents[0], true);
3798 						parent.insert(node, newParent);
3799 					} else {
3800 						parent.insert(node, parents[0], true);
3801 					}
3802 
3803 					// Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
3804 					parent = parents[0];
3805 					if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
3806 						parent.empty().remove();
3807 					}
3808 				} else if (node.parent) {
3809 					// If it's an LI try to find a UL/OL for it or wrap it
3810 					if (node.name === 'li') {
3811 						sibling = node.prev;
3812 						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3813 							sibling.append(node);
3814 							continue;
3815 						}
3816 
3817 						sibling = node.next;
3818 						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3819 							sibling.insert(node, sibling.firstChild, true);
3820 							continue;
3821 						}
3822 
3823 						node.wrap(self.filterNode(new Node('ul', 1)));
3824 						continue;
3825 					}
3826 
3827 					// Try wrapping the element in a DIV
3828 					if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
3829 						node.wrap(self.filterNode(new Node('div', 1)));
3830 					} else {
3831 						// We failed wrapping it, then remove or unwrap it
3832 						if (node.name === 'style' || node.name === 'script')
3833 							node.empty().remove();
3834 						else
3835 							node.unwrap();
3836 					}
3837 				}
3838 			}
3839 		};
3840 
3841 		self.filterNode = function(node) {
3842 			var i, name, list;
3843 
3844 			// Run element filters
3845 			if (name in nodeFilters) {
3846 				list = matchedNodes[name];
3847 
3848 				if (list)
3849 					list.push(node);
3850 				else
3851 					matchedNodes[name] = [node];
3852 			}
3853 
3854 			// Run attribute filters
3855 			i = attributeFilters.length;
3856 			while (i--) {
3857 				name = attributeFilters[i].name;
3858 
3859 				if (name in node.attributes.map) {
3860 					list = matchedAttributes[name];
3861 
3862 					if (list)
3863 						list.push(node);
3864 					else
3865 						matchedAttributes[name] = [node];
3866 				}
3867 			}
3868 
3869 			return node;
3870 		};
3871 
3872 		self.addNodeFilter = function(name, callback) {
3873 			tinymce.each(tinymce.explode(name), function(name) {
3874 				var list = nodeFilters[name];
3875 
3876 				if (!list)
3877 					nodeFilters[name] = list = [];
3878 
3879 				list.push(callback);
3880 			});
3881 		};
3882 
3883 		self.addAttributeFilter = function(name, callback) {
3884 			tinymce.each(tinymce.explode(name), function(name) {
3885 				var i;
3886 
3887 				for (i = 0; i < attributeFilters.length; i++) {
3888 					if (attributeFilters[i].name === name) {
3889 						attributeFilters[i].callbacks.push(callback);
3890 						return;
3891 					}
3892 				}
3893 
3894 				attributeFilters.push({name: name, callbacks: [callback]});
3895 			});
3896 		};
3897 
3898 		self.parse = function(html, args) {
3899 			var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
3900 				blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement,
3901 				endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
3902 
3903 			args = args || {};
3904 			matchedNodes = {};
3905 			matchedAttributes = {};
3906 			blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
3907 			nonEmptyElements = schema.getNonEmptyElements();
3908 			children = schema.children;
3909 			validate = settings.validate;
3910 			rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
3911 
3912 			whiteSpaceElements = schema.getWhiteSpaceElements();
3913 			startWhiteSpaceRegExp = /^[ \t\r\n]+/;
3914 			endWhiteSpaceRegExp = /[ \t\r\n]+$/;
3915 			allWhiteSpaceRegExp = /[ \t\r\n]+/g;
3916 			isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
3917 
3918 			function addRootBlocks() {
3919 				var node = rootNode.firstChild, next, rootBlockNode;
3920 
3921 				while (node) {
3922 					next = node.next;
3923 
3924 					if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
3925 						if (!rootBlockNode) {
3926 							// Create a new root block element
3927 							rootBlockNode = createNode(rootBlockName, 1);
3928 							rootNode.insert(rootBlockNode, node);
3929 							rootBlockNode.append(node);
3930 						} else
3931 							rootBlockNode.append(node);
3932 					} else {
3933 						rootBlockNode = null;
3934 					}
3935 
3936 					node = next;
3937 				};
3938 			};
3939 
3940 			function createNode(name, type) {
3941 				var node = new Node(name, type), list;
3942 
3943 				if (name in nodeFilters) {
3944 					list = matchedNodes[name];
3945 
3946 					if (list)
3947 						list.push(node);
3948 					else
3949 						matchedNodes[name] = [node];
3950 				}
3951 
3952 				return node;
3953 			};
3954 
3955 			function removeWhitespaceBefore(node) {
3956 				var textNode, textVal, sibling;
3957 
3958 				for (textNode = node.prev; textNode && textNode.type === 3; ) {
3959 					textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
3960 
3961 					if (textVal.length > 0) {
3962 						textNode.value = textVal;
3963 						textNode = textNode.prev;
3964 					} else {
3965 						sibling = textNode.prev;
3966 						textNode.remove();
3967 						textNode = sibling;
3968 					}
3969 				}
3970 			};
3971 
3972 			function cloneAndExcludeBlocks(input) {
3973 				var name, output = {};
3974 
3975 				for (name in input) {
3976 					if (name !== 'li' && name != 'p') {
3977 						output[name] = input[name];
3978 					}
3979 				}
3980 
3981 				return output;
3982 			};
3983 
3984 			parser = new tinymce.html.SaxParser({
3985 				validate : validate,
3986 
3987 				// Exclude P and LI from DOM parsing since it's treated better by the DOM parser
3988 				self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
3989 
3990 				cdata: function(text) {
3991 					node.append(createNode('#cdata', 4)).value = text;
3992 				},
3993 
3994 				text: function(text, raw) {
3995 					var textNode;
3996 
3997 					// Trim all redundant whitespace on non white space elements
3998 					if (!isInWhiteSpacePreservedElement) {
3999 						text = text.replace(allWhiteSpaceRegExp, ' ');
4000 
4001 						if (node.lastChild && blockElements[node.lastChild.name])
4002 							text = text.replace(startWhiteSpaceRegExp, '');
4003 					}
4004 
4005 					// Do we need to create the node
4006 					if (text.length !== 0) {
4007 						textNode = createNode('#text', 3);
4008 						textNode.raw = !!raw;
4009 						node.append(textNode).value = text;
4010 					}
4011 				},
4012 
4013 				comment: function(text) {
4014 					node.append(createNode('#comment', 8)).value = text;
4015 				},
4016 
4017 				pi: function(name, text) {
4018 					node.append(createNode(name, 7)).value = text;
4019 					removeWhitespaceBefore(node);
4020 				},
4021 
4022 				doctype: function(text) {
4023 					var newNode;
4024 		
4025 					newNode = node.append(createNode('#doctype', 10));
4026 					newNode.value = text;
4027 					removeWhitespaceBefore(node);
4028 				},
4029 
4030 				start: function(name, attrs, empty) {
4031 					var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
4032 
4033 					elementRule = validate ? schema.getElementRule(name) : {};
4034 					if (elementRule) {
4035 						newNode = createNode(elementRule.outputName || name, 1);
4036 						newNode.attributes = attrs;
4037 						newNode.shortEnded = empty;
4038 
4039 						node.append(newNode);
4040 
4041 						// Check if node is valid child of the parent node is the child is
4042 						// unknown we don't collect it since it's probably a custom element
4043 						parent = children[node.name];
4044 						if (parent && children[newNode.name] && !parent[newNode.name])
4045 							invalidChildren.push(newNode);
4046 
4047 						attrFiltersLen = attributeFilters.length;
4048 						while (attrFiltersLen--) {
4049 							attrName = attributeFilters[attrFiltersLen].name;
4050 
4051 							if (attrName in attrs.map) {
4052 								list = matchedAttributes[attrName];
4053 
4054 								if (list)
4055 									list.push(newNode);
4056 								else
4057 									matchedAttributes[attrName] = [newNode];
4058 							}
4059 						}
4060 
4061 						// Trim whitespace before block
4062 						if (blockElements[name])
4063 							removeWhitespaceBefore(newNode);
4064 
4065 						// Change current node if the element wasn't empty i.e not <br /> or <img />
4066 						if (!empty)
4067 							node = newNode;
4068 
4069 						// Check if we are inside a whitespace preserved element
4070 						if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
4071 							isInWhiteSpacePreservedElement = true;
4072 						}
4073 					}
4074 				},
4075 
4076 				end: function(name) {
4077 					var textNode, elementRule, text, sibling, tempNode;
4078 
4079 					elementRule = validate ? schema.getElementRule(name) : {};
4080 					if (elementRule) {
4081 						if (blockElements[name]) {
4082 							if (!isInWhiteSpacePreservedElement) {
4083 								// Trim whitespace of the first node in a block
4084 								textNode = node.firstChild;
4085 								if (textNode && textNode.type === 3) {
4086 									text = textNode.value.replace(startWhiteSpaceRegExp, '');
4087 
4088 									// Any characters left after trim or should we remove it
4089 									if (text.length > 0) {
4090 										textNode.value = text;
4091 										textNode = textNode.next;
4092 									} else {
4093 										sibling = textNode.next;
4094 										textNode.remove();
4095 										textNode = sibling;
4096 									}
4097 
4098 									// Remove any pure whitespace siblings
4099 									while (textNode && textNode.type === 3) {
4100 										text = textNode.value;
4101 										sibling = textNode.next;
4102 
4103 										if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
4104 											textNode.remove();
4105 											textNode = sibling;
4106 										}
4107 
4108 										textNode = sibling;
4109 									}
4110 								}
4111 
4112 								// Trim whitespace of the last node in a block
4113 								textNode = node.lastChild;
4114 								if (textNode && textNode.type === 3) {
4115 									text = textNode.value.replace(endWhiteSpaceRegExp, '');
4116 
4117 									// Any characters left after trim or should we remove it
4118 									if (text.length > 0) {
4119 										textNode.value = text;
4120 										textNode = textNode.prev;
4121 									} else {
4122 										sibling = textNode.prev;
4123 										textNode.remove();
4124 										textNode = sibling;
4125 									}
4126 
4127 									// Remove any pure whitespace siblings
4128 									while (textNode && textNode.type === 3) {
4129 										text = textNode.value;
4130 										sibling = textNode.prev;
4131 
4132 										if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
4133 											textNode.remove();
4134 											textNode = sibling;
4135 										}
4136 
4137 										textNode = sibling;
4138 									}
4139 								}
4140 							}
4141 
4142 							// Trim start white space
4143 							textNode = node.prev;
4144 							if (textNode && textNode.type === 3) {
4145 								text = textNode.value.replace(startWhiteSpaceRegExp, '');
4146 
4147 								if (text.length > 0)
4148 									textNode.value = text;
4149 								else
4150 									textNode.remove();
4151 							}
4152 						}
4153 
4154 						// Check if we exited a whitespace preserved element
4155 						if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
4156 							isInWhiteSpacePreservedElement = false;
4157 						}
4158 
4159 						// Handle empty nodes
4160 						if (elementRule.removeEmpty || elementRule.paddEmpty) {
4161 							if (node.isEmpty(nonEmptyElements)) {
4162 								if (elementRule.paddEmpty)
4163 									node.empty().append(new Node('#text', '3')).value = '\u00a0';
4164 								else {
4165 									// Leave nodes that have a name like <a name="name">
4166 									if (!node.attributes.map.name && !node.attributes.map.id) {
4167 										tempNode = node.parent;
4168 										node.empty().remove();
4169 										node = tempNode;
4170 										return;
4171 									}
4172 								}
4173 							}
4174 						}
4175 
4176 						node = node.parent;
4177 					}
4178 				}
4179 			}, schema);
4180 
4181 			rootNode = node = new Node(args.context || settings.root_name, 11);
4182 
4183 			parser.parse(html);
4184 
4185 			// Fix invalid children or report invalid children in a contextual parsing
4186 			if (validate && invalidChildren.length) {
4187 				if (!args.context)
4188 					fixInvalidChildren(invalidChildren);
4189 				else
4190 					args.invalid = true;
4191 			}
4192 
4193 			// Wrap nodes in the root into block elements if the root is body
4194 			if (rootBlockName && rootNode.name == 'body')
4195 				addRootBlocks();
4196 
4197 			// Run filters only when the contents is valid
4198 			if (!args.invalid) {
4199 				// Run node filters
4200 				for (name in matchedNodes) {
4201 					list = nodeFilters[name];
4202 					nodes = matchedNodes[name];
4203 
4204 					// Remove already removed children
4205 					fi = nodes.length;
4206 					while (fi--) {
4207 						if (!nodes[fi].parent)
4208 							nodes.splice(fi, 1);
4209 					}
4210 
4211 					for (i = 0, l = list.length; i < l; i++)
4212 						list[i](nodes, name, args);
4213 				}
4214 
4215 				// Run attribute filters
4216 				for (i = 0, l = attributeFilters.length; i < l; i++) {
4217 					list = attributeFilters[i];
4218 
4219 					if (list.name in matchedAttributes) {
4220 						nodes = matchedAttributes[list.name];
4221 
4222 						// Remove already removed children
4223 						fi = nodes.length;
4224 						while (fi--) {
4225 							if (!nodes[fi].parent)
4226 								nodes.splice(fi, 1);
4227 						}
4228 
4229 						for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
4230 							list.callbacks[fi](nodes, list.name, args);
4231 					}
4232 				}
4233 			}
4234 
4235 			return rootNode;
4236 		};
4237 
4238 		// Remove <br> at end of block elements Gecko and WebKit injects BR elements to
4239 		// make it possible to place the caret inside empty blocks. This logic tries to remove
4240 		// these elements and keep br elements that where intended to be there intact
4241 		if (settings.remove_trailing_brs) {
4242 			self.addNodeFilter('br', function(nodes, name) {
4243 				var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()),
4244 					nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
4245 
4246 				// Remove brs from body element as well
4247 				blockElements.body = 1;
4248 
4249 				// Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
4250 				for (i = 0; i < l; i++) {
4251 					node = nodes[i];
4252 					parent = node.parent;
4253 
4254 					if (blockElements[node.parent.name] && node === parent.lastChild) {
4255 						// Loop all nodes to the left of the current node and check for other BR elements
4256 						// excluding bookmarks since they are invisible
4257 						prev = node.prev;
4258 						while (prev) {
4259 							prevName = prev.name;
4260 
4261 							// Ignore bookmarks
4262 							if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
4263 								// Found a non BR element
4264 								if (prevName !== "br")
4265 									break;
4266 	
4267 								// Found another br it's a <br><br> structure then don't remove anything
4268 								if (prevName === 'br') {
4269 									node = null;
4270 									break;
4271 								}
4272 							}
4273 
4274 							prev = prev.prev;
4275 						}
4276 
4277 						if (node) {
4278 							node.remove();
4279 
4280 							// Is the parent to be considered empty after we removed the BR
4281 							if (parent.isEmpty(nonEmptyElements)) {
4282 								elementRule = schema.getElementRule(parent.name);
4283 
4284 								// Remove or padd the element depending on schema rule
4285 								if (elementRule) {
4286 									if (elementRule.removeEmpty)
4287 										parent.remove();
4288 									else if (elementRule.paddEmpty)
4289 										parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
4290 								}
4291 							}
4292 						}
4293 					} else {
4294 						// Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i> </i></b></p> 
4295 						lastParent = node;
4296 						while (parent.firstChild === lastParent && parent.lastChild === lastParent) {
4297 							lastParent = parent;
4298 
4299 							if (blockElements[parent.name]) {
4300 								break;
4301 							}
4302 
4303 							parent = parent.parent;
4304 						}
4305 
4306 						if (lastParent === parent) {
4307 							textNode = new tinymce.html.Node('#text', 3);
4308 							textNode.value = '\u00a0';
4309 							node.replace(textNode);
4310 						}
4311 					}
4312 				}
4313 			});
4314 		}
4315 
4316 		// Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
4317 		if (!settings.allow_html_in_named_anchor) {
4318 			self.addAttributeFilter('id,name', function(nodes, name) {
4319 				var i = nodes.length, sibling, prevSibling, parent, node;
4320 
4321 				while (i--) {
4322 					node = nodes[i];
4323 					if (node.name === 'a' && node.firstChild && !node.attr('href')) {
4324 						parent = node.parent;
4325 
4326 						// Move children after current node
4327 						sibling = node.lastChild;
4328 						do {
4329 							prevSibling = sibling.prev;
4330 							parent.insert(sibling, node);
4331 							sibling = prevSibling;
4332 						} while (sibling);
4333 					}
4334 				}
4335 			});
4336 		}
4337 	}
4338 })(tinymce);
4339 
4340 tinymce.html.Writer = function(settings) {
4341 	var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
4342 
4343 	settings = settings || {};
4344 	indent = settings.indent;
4345 	indentBefore = tinymce.makeMap(settings.indent_before || '');
4346 	indentAfter = tinymce.makeMap(settings.indent_after || '');
4347 	encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
4348 	htmlOutput = settings.element_format == "html";
4349 
4350 	return {
4351 		start: function(name, attrs, empty) {
4352 			var i, l, attr, value;
4353 
4354 			if (indent && indentBefore[name] && html.length > 0) {
4355 				value = html[html.length - 1];
4356 
4357 				if (value.length > 0 && value !== '\n')
4358 					html.push('\n');
4359 			}
4360 
4361 			html.push('<', name);
4362 
4363 			if (attrs) {
4364 				for (i = 0, l = attrs.length; i < l; i++) {
4365 					attr = attrs[i];
4366 					html.push(' ', attr.name, '="', encode(attr.value, true), '"');
4367 				}
4368 			}
4369 
4370 			if (!empty || htmlOutput)
4371 				html[html.length] = '>';
4372 			else
4373 				html[html.length] = ' />';
4374 
4375 			if (empty && indent && indentAfter[name] && html.length > 0) {
4376 				value = html[html.length - 1];
4377 
4378 				if (value.length > 0 && value !== '\n')
4379 					html.push('\n');
4380 			}
4381 		},
4382 
4383 		end: function(name) {
4384 			var value;
4385 
4386 			/*if (indent && indentBefore[name] && html.length > 0) {
4387 				value = html[html.length - 1];
4388 
4389 				if (value.length > 0 && value !== '\n')
4390 					html.push('\n');
4391 			}*/
4392 
4393 			html.push('</', name, '>');
4394 
4395 			if (indent && indentAfter[name] && html.length > 0) {
4396 				value = html[html.length - 1];
4397 
4398 				if (value.length > 0 && value !== '\n')
4399 					html.push('\n');
4400 			}
4401 		},
4402 
4403 		text: function(text, raw) {
4404 			if (text.length > 0)
4405 				html[html.length] = raw ? text : encode(text);
4406 		},
4407 
4408 		cdata: function(text) {
4409 			html.push('<![CDATA[', text, ']]>');
4410 		},
4411 
4412 		comment: function(text) {
4413 			html.push('<!--', text, '-->');
4414 		},
4415 
4416 		pi: function(name, text) {
4417 			if (text)
4418 				html.push('<?', name, ' ', text, '?>');
4419 			else
4420 				html.push('<?', name, '?>');
4421 
4422 			if (indent)
4423 				html.push('\n');
4424 		},
4425 
4426 		doctype: function(text) {
4427 			html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
4428 		},
4429 
4430 		reset: function() {
4431 			html.length = 0;
4432 		},
4433 
4434 		getContent: function() {
4435 			return html.join('').replace(/\n$/, '');
4436 		}
4437 	};
4438 };
4439 
4440 (function(tinymce) {
4441 	tinymce.html.Serializer = function(settings, schema) {
4442 		var self = this, writer = new tinymce.html.Writer(settings);
4443 
4444 		settings = settings || {};
4445 		settings.validate = "validate" in settings ? settings.validate : true;
4446 
4447 		self.schema = schema = schema || new tinymce.html.Schema();
4448 		self.writer = writer;
4449 
4450 		self.serialize = function(node) {
4451 			var handlers, validate;
4452 
4453 			validate = settings.validate;
4454 
4455 			handlers = {
4456 				// #text
4457 				3: function(node, raw) {
4458 					writer.text(node.value, node.raw);
4459 				},
4460 
4461 				// #comment
4462 				8: function(node) {
4463 					writer.comment(node.value);
4464 				},
4465 
4466 				// Processing instruction
4467 				7: function(node) {
4468 					writer.pi(node.name, node.value);
4469 				},
4470 
4471 				// Doctype
4472 				10: function(node) {
4473 					writer.doctype(node.value);
4474 				},
4475 
4476 				// CDATA
4477 				4: function(node) {
4478 					writer.cdata(node.value);
4479 				},
4480 
4481 				// Document fragment
4482 				11: function(node) {
4483 					if ((node = node.firstChild)) {
4484 						do {
4485 							walk(node);
4486 						} while (node = node.next);
4487 					}
4488 				}
4489 			};
4490 
4491 			writer.reset();
4492 
4493 			function walk(node) {
4494 				var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
4495 
4496 				if (!handler) {
4497 					name = node.name;
4498 					isEmpty = node.shortEnded;
4499 					attrs = node.attributes;
4500 
4501 					// Sort attributes
4502 					if (validate && attrs && attrs.length > 1) {
4503 						sortedAttrs = [];
4504 						sortedAttrs.map = {};
4505 
4506 						elementRule = schema.getElementRule(node.name);
4507 						for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
4508 							attrName = elementRule.attributesOrder[i];
4509 
4510 							if (attrName in attrs.map) {
4511 								attrValue = attrs.map[attrName];
4512 								sortedAttrs.map[attrName] = attrValue;
4513 								sortedAttrs.push({name: attrName, value: attrValue});
4514 							}
4515 						}
4516 
4517 						for (i = 0, l = attrs.length; i < l; i++) {
4518 							attrName = attrs[i].name;
4519 
4520 							if (!(attrName in sortedAttrs.map)) {
4521 								attrValue = attrs.map[attrName];
4522 								sortedAttrs.map[attrName] = attrValue;
4523 								sortedAttrs.push({name: attrName, value: attrValue});
4524 							}
4525 						}
4526 
4527 						attrs = sortedAttrs;
4528 					}
4529 
4530 					writer.start(node.name, attrs, isEmpty);
4531 
4532 					if (!isEmpty) {
4533 						if ((node = node.firstChild)) {
4534 							do {
4535 								walk(node);
4536 							} while (node = node.next);
4537 						}
4538 
4539 						writer.end(name);
4540 					}
4541 				} else
4542 					handler(node);
4543 			}
4544 
4545 			// Serialize element and treat all non elements as fragments
4546 			if (node.type == 1 && !settings.inner)
4547 				walk(node);
4548 			else
4549 				handlers[11](node);
4550 
4551 			return writer.getContent();
4552 		};
4553 	}
4554 })(tinymce);
4555 
4556 // JSLint defined globals
4557 /*global tinymce:false, window:false */
4558 
4559 tinymce.dom = {};
4560 
4561 (function(namespace, expando) {
4562 	var w3cEventModel = !!document.addEventListener;
4563 
4564 	function addEvent(target, name, callback, capture) {
4565 		if (target.addEventListener) {
4566 			target.addEventListener(name, callback, capture || false);
4567 		} else if (target.attachEvent) {
4568 			target.attachEvent('on' + name, callback);
4569 		}
4570 	}
4571 
4572 	function removeEvent(target, name, callback, capture) {
4573 		if (target.removeEventListener) {
4574 			target.removeEventListener(name, callback, capture || false);
4575 		} else if (target.detachEvent) {
4576 			target.detachEvent('on' + name, callback);
4577 		}
4578 	}
4579 
4580 	function fix(original_event, data) {
4581 		var name, event = data || {};
4582 
4583 		// Dummy function that gets replaced on the delegation state functions
4584 		function returnFalse() {
4585 			return false;
4586 		}
4587 
4588 		// Dummy function that gets replaced on the delegation state functions
4589 		function returnTrue() {
4590 			return true;
4591 		}
4592 
4593 		// Copy all properties from the original event
4594 		for (name in original_event) {
4595 			// layerX/layerY is deprecated in Chrome and produces a warning
4596 			if (name !== "layerX" && name !== "layerY") {
4597 				event[name] = original_event[name];
4598 			}
4599 		}
4600 
4601 		// Normalize target IE uses srcElement
4602 		if (!event.target) {
4603 			event.target = event.srcElement || document;
4604 		}
4605 
4606 		// Add preventDefault method
4607 		event.preventDefault = function() {
4608 			event.isDefaultPrevented = returnTrue;
4609 
4610 			// Execute preventDefault on the original event object
4611 			if (original_event) {
4612 				if (original_event.preventDefault) {
4613 					original_event.preventDefault();
4614 				} else {
4615 					original_event.returnValue = false; // IE
4616 				}
4617 			}
4618 		};
4619 
4620 		// Add stopPropagation
4621 		event.stopPropagation = function() {
4622 			event.isPropagationStopped = returnTrue;
4623 
4624 			// Execute stopPropagation on the original event object
4625 			if (original_event) {
4626 				if (original_event.stopPropagation) {
4627 					original_event.stopPropagation();
4628 				} else {
4629 					original_event.cancelBubble = true; // IE
4630 				}
4631 			 }
4632 		};
4633 
4634 		// Add stopImmediatePropagation
4635 		event.stopImmediatePropagation = function() {
4636 			event.isImmediatePropagationStopped = returnTrue;
4637 			event.stopPropagation();
4638 		};
4639 
4640 		// Add event delegation states
4641 		if (!event.isDefaultPrevented) {
4642 			event.isDefaultPrevented = returnFalse;
4643 			event.isPropagationStopped = returnFalse;
4644 			event.isImmediatePropagationStopped = returnFalse;
4645 		}
4646 
4647 		return event;
4648 	}
4649 
4650 	function bindOnReady(win, callback, event_utils) {
4651 		var doc = win.document, event = {type: 'ready'};
4652 
4653 		// Gets called when the DOM is ready
4654 		function readyHandler() {
4655 			if (!event_utils.domLoaded) {
4656 				event_utils.domLoaded = true;
4657 				callback(event);
4658 			}
4659 		}
4660 
4661 		// Use W3C method
4662 		if (w3cEventModel) {
4663 			addEvent(win, 'DOMContentLoaded', readyHandler);
4664 		} else {
4665 			// Use IE method
4666 			addEvent(doc, "readystatechange", function() {
4667 				if (doc.readyState === "complete") {
4668 					removeEvent(doc, "readystatechange", arguments.callee);
4669 					readyHandler();
4670 				}
4671 			});
4672 
4673 			// Wait until we can scroll, when we can the DOM is initialized
4674 			if (doc.documentElement.doScroll && win === win.top) {
4675 				(function() {
4676 					try {
4677 						// If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
4678 						// http://javascript.nwbox.com/IEContentLoaded/
4679 						doc.documentElement.doScroll("left");
4680 					} catch (ex) {
4681 						setTimeout(arguments.callee, 0);
4682 						return;
4683 					}
4684 
4685 					readyHandler();
4686 				})();
4687 			}
4688 		}
4689 
4690 		// Fallback if any of the above methods should fail for some odd reason
4691 		addEvent(win, 'load', readyHandler);
4692 	}
4693 
4694 	function EventUtils(proxy) {
4695 		var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
4696 
4697 		hasMouseEnterLeave = "onmouseenter" in document.documentElement;
4698 		hasFocusIn = "onfocusin" in document.documentElement;
4699 		mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
4700 		count = 1;
4701 
4702 		// State if the DOMContentLoaded was executed or not
4703 		self.domLoaded = false;
4704 		self.events = events;
4705 
4706 		function executeHandlers(evt, id) {
4707 			var callbackList, i, l, callback;
4708 
4709 			callbackList = events[id][evt.type];
4710 			if (callbackList) {
4711 				for (i = 0, l = callbackList.length; i < l; i++) {
4712 					callback = callbackList[i];
4713 					
4714 					// Check if callback exists might be removed if a unbind is called inside the callback
4715 					if (callback && callback.func.call(callback.scope, evt) === false) {
4716 						evt.preventDefault();
4717 					}
4718 
4719 					// Should we stop propagation to immediate listeners
4720 					if (evt.isImmediatePropagationStopped()) {
4721 						return;
4722 					}
4723 				}
4724 			}
4725 		}
4726 
4727 		self.bind = function(target, names, callback, scope) {
4728 			var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
4729 
4730 			// Native event handler function patches the event and executes the callbacks for the expando
4731 			function defaultNativeHandler(evt) {
4732 				executeHandlers(fix(evt || win.event), id);
4733 			}
4734 
4735 			// Don't bind to text nodes or comments
4736 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4737 				return;
4738 			}
4739 
4740 			// Create or get events id for the target
4741 			if (!target[expando]) {
4742 				id = count++;
4743 				target[expando] = id;
4744 				events[id] = {};
4745 			} else {
4746 				id = target[expando];
4747 
4748 				if (!events[id]) {
4749 					events[id] = {};
4750 				}
4751 			}
4752 
4753 			// Setup the specified scope or use the target as a default
4754 			scope = scope || target;
4755 
4756 			// Split names and bind each event, enables you to bind multiple events with one call
4757 			names = names.split(' ');
4758 			i = names.length;
4759 			while (i--) {
4760 				name = names[i];
4761 				nativeHandler = defaultNativeHandler;
4762 				fakeName = capture = false;
4763 
4764 				// Use ready instead of DOMContentLoaded
4765 				if (name === "DOMContentLoaded") {
4766 					name = "ready";
4767 				}
4768 
4769 				// DOM is already ready
4770 				if ((self.domLoaded || target.readyState == 'complete') && name === "ready") {
4771 					self.domLoaded = true;
4772 					callback.call(scope, fix({type: name}));
4773 					continue;
4774 				}
4775 
4776 				// Handle mouseenter/mouseleaver
4777 				if (!hasMouseEnterLeave) {
4778 					fakeName = mouseEnterLeave[name];
4779 
4780 					if (fakeName) {
4781 						nativeHandler = function(evt) {
4782 							var current, related;
4783 
4784 							current = evt.currentTarget;
4785 							related = evt.relatedTarget;
4786 
4787 							// Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element
4788 							if (related && current.contains) {
4789 								// Use contains for performance
4790 								related = current.contains(related);
4791 							} else {
4792 								while (related && related !== current) {
4793 									related = related.parentNode;
4794 								}
4795 							}
4796 
4797 							// Fire fake event
4798 							if (!related) {
4799 								evt = fix(evt || win.event);
4800 								evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
4801 								evt.target = current;
4802 								executeHandlers(evt, id);
4803 							}
4804 						};
4805 					}
4806 				}
4807 
4808 				// Fake bubbeling of focusin/focusout
4809 				if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
4810 					capture = true;
4811 					fakeName = name === "focusin" ? "focus" : "blur";
4812 					nativeHandler = function(evt) {
4813 						evt = fix(evt || win.event);
4814 						evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
4815 						executeHandlers(evt, id);
4816 					};
4817 				}
4818 
4819 				// Setup callback list and bind native event
4820 				callbackList = events[id][name];
4821 				if (!callbackList) {
4822 					events[id][name] = callbackList = [{func: callback, scope: scope}];
4823 					callbackList.fakeName = fakeName;
4824 					callbackList.capture = capture;
4825 
4826 					// Add the nativeHandler to the callback list so that we can later unbind it
4827 					callbackList.nativeHandler = nativeHandler;
4828 					if (!w3cEventModel) {
4829 						callbackList.proxyHandler = proxy(id);
4830 					}
4831 
4832 					// Check if the target has native events support
4833 					if (name === "ready") {
4834 						bindOnReady(target, nativeHandler, self);
4835 					} else {
4836 						addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture);
4837 					}
4838 				} else {
4839 					// If it already has an native handler then just push the callback
4840 					callbackList.push({func: callback, scope: scope});
4841 				}
4842 			}
4843 
4844 			target = callbackList = 0; // Clean memory for IE
4845 
4846 			return callback;
4847 		};
4848 
4849 		self.unbind = function(target, names, callback) {
4850 			var id, callbackList, i, ci, name, eventMap;
4851 
4852 			// Don't bind to text nodes or comments
4853 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4854 				return self;
4855 			}
4856 
4857 			// Unbind event or events if the target has the expando
4858 			id = target[expando];
4859 			if (id) {
4860 				eventMap = events[id];
4861 
4862 				// Specific callback
4863 				if (names) {
4864 					names = names.split(' ');
4865 					i = names.length;
4866 					while (i--) {
4867 						name = names[i];
4868 						callbackList = eventMap[name];
4869 
4870 						// Unbind the event if it exists in the map
4871 						if (callbackList) {
4872 							// Remove specified callback
4873 							if (callback) {
4874 								ci = callbackList.length;
4875 								while (ci--) {
4876 									if (callbackList[ci].func === callback) {
4877 										callbackList.splice(ci, 1);
4878 									}
4879 								}
4880 							}
4881 
4882 							// Remove all callbacks if there isn't a specified callback or there is no callbacks left
4883 							if (!callback || callbackList.length === 0) {
4884 								delete eventMap[name];
4885 								removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4886 							}
4887 						}
4888 					}
4889 				} else {
4890 					// All events for a specific element
4891 					for (name in eventMap) {
4892 						callbackList = eventMap[name];
4893 						removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4894 					}
4895 
4896 					eventMap = {};
4897 				}
4898 
4899 				// Check if object is empty, if it isn't then we won't remove the expando map
4900 				for (name in eventMap) {
4901 					return self;
4902 				}
4903 
4904 				// Delete event object
4905 				delete events[id];
4906 
4907 				// Remove expando from target
4908 				try {
4909 					// IE will fail here since it can't delete properties from window
4910 					delete target[expando];
4911 				} catch (ex) {
4912 					// IE will set it to null
4913 					target[expando] = null;
4914 				}
4915 			}
4916 
4917 			return self;
4918 		};
4919 
4920 		self.fire = function(target, name, args) {
4921 			var id, event;
4922 
4923 			// Don't bind to text nodes or comments
4924 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4925 				return self;
4926 			}
4927 
4928 			// Build event object by patching the args
4929 			event = fix(null, args);
4930 			event.type = name;
4931 
4932 			do {
4933 				// Found an expando that means there is listeners to execute
4934 				id = target[expando];
4935 				if (id) {
4936 					executeHandlers(event, id);
4937 				}
4938 
4939 				// Walk up the DOM
4940 				target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
4941 			} while (target && !event.isPropagationStopped());
4942 
4943 			return self;
4944 		};
4945 
4946 		self.clean = function(target) {
4947 			var i, children, unbind = self.unbind;
4948 	
4949 			// Don't bind to text nodes or comments
4950 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4951 				return self;
4952 			}
4953 
4954 			// Unbind any element on the specificed target
4955 			if (target[expando]) {
4956 				unbind(target);
4957 			}
4958 
4959 			// Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
4960 			if (!target.getElementsByTagName) {
4961 				target = target.document;
4962 			}
4963 
4964 			// Remove events from each child element
4965 			if (target && target.getElementsByTagName) {
4966 				unbind(target);
4967 
4968 				children = target.getElementsByTagName('*');
4969 				i = children.length;
4970 				while (i--) {
4971 					target = children[i];
4972 
4973 					if (target[expando]) {
4974 						unbind(target);
4975 					}
4976 				}
4977 			}
4978 
4979 			return self;
4980 		};
4981 
4982 		self.callNativeHandler = function(id, evt) {
4983 			if (events) {
4984 				events[id][evt.type].nativeHandler(evt);
4985 			}
4986 		};
4987 
4988 		self.destory = function() {
4989 			events = {};
4990 		};
4991 
4992 		// Legacy function calls
4993 
4994 		self.add = function(target, events, func, scope) {
4995 			// Old API supported direct ID assignment
4996 			if (typeof(target) === "string") {
4997 				target = document.getElementById(target);
4998 			}
4999 
5000 			// Old API supported multiple targets
5001 			if (target && target instanceof Array) {
5002 				var i = target.length;
5003 
5004 				while (i--) {
5005 					self.add(target[i], events, func, scope);
5006 				}
5007 
5008 				return;
5009 			}
5010 
5011 			// Old API called ready init
5012 			if (events === "init") {
5013 				events = "ready";
5014 			}
5015 
5016 			return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope);
5017 		};
5018 
5019 		self.remove = function(target, events, func, scope) {
5020 			if (!target) {
5021 				return self;
5022 			}
5023 
5024 			// Old API supported direct ID assignment
5025 			if (typeof(target) === "string") {
5026 				target = document.getElementById(target);
5027 			}
5028 
5029 			// Old API supported multiple targets
5030 			if (target instanceof Array) {
5031 				var i = target.length;
5032 
5033 				while (i--) {
5034 					self.remove(target[i], events, func, scope);
5035 				}
5036 
5037 				return self;
5038 			}
5039 
5040 			return self.unbind(target, events instanceof Array ? events.join(' ') : events, func);
5041 		};
5042 
5043 		self.clear = function(target) {
5044 			// Old API supported direct ID assignment
5045 			if (typeof(target) === "string") {
5046 				target = document.getElementById(target);
5047 			}
5048 
5049 			return self.clean(target);
5050 		};
5051 
5052 		self.cancel = function(e) {
5053 			if (e) {
5054 				self.prevent(e);
5055 				self.stop(e);
5056 			}
5057 
5058 			return false;
5059 		};
5060 
5061 		self.prevent = function(e) {
5062 			if (!e.preventDefault) {
5063 				e = fix(e);
5064 			}
5065 
5066 			e.preventDefault();
5067 
5068 			return false;
5069 		};
5070 
5071 		self.stop = function(e) {
5072 			if (!e.stopPropagation) {
5073 				e = fix(e);
5074 			}
5075 
5076 			e.stopPropagation();
5077 
5078 			return false;
5079 		};
5080 	}
5081 
5082 	namespace.EventUtils = EventUtils;
5083 
5084 	namespace.Event = new EventUtils(function(id) {
5085 		return function(evt) {
5086 			tinymce.dom.Event.callNativeHandler(id, evt);
5087 		};
5088 	});
5089 
5090 	// Bind ready event when tinymce script is loaded
5091 	namespace.Event.bind(window, 'ready', function() {});
5092 
5093 	namespace = 0;
5094 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando
5095 
5096 tinymce.dom.TreeWalker = function(start_node, root_node) {
5097 	var node = start_node;
5098 
5099 	function findSibling(node, start_name, sibling_name, shallow) {
5100 		var sibling, parent;
5101 
5102 		if (node) {
5103 			// Walk into nodes if it has a start
5104 			if (!shallow && node[start_name])
5105 				return node[start_name];
5106 
5107 			// Return the sibling if it has one
5108 			if (node != root_node) {
5109 				sibling = node[sibling_name];
5110 				if (sibling)
5111 					return sibling;
5112 
5113 				// Walk up the parents to look for siblings
5114 				for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
5115 					sibling = parent[sibling_name];
5116 					if (sibling)
5117 						return sibling;
5118 				}
5119 			}
5120 		}
5121 	};
5122 
5123 	this.current = function() {
5124 		return node;
5125 	};
5126 
5127 	this.next = function(shallow) {
5128 		return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
5129 	};
5130 
5131 	this.prev = function(shallow) {
5132 		return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
5133 	};
5134 };
5135 
5136 (function(tinymce) {
5137 	// Shorten names
5138 	var each = tinymce.each,
5139 		is = tinymce.is,
5140 		isWebKit = tinymce.isWebKit,
5141 		isIE = tinymce.isIE,
5142 		Entities = tinymce.html.Entities,
5143 		simpleSelectorRe = /^([a-z0-9],?)+$/i,
5144 		whiteSpaceRegExp = /^[ \t\r\n]*$/;
5145 
5146 	tinymce.create('tinymce.dom.DOMUtils', {
5147 		doc : null,
5148 		root : null,
5149 		files : null,
5150 		pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
5151 		props : {
5152 			"for" : "htmlFor",
5153 			"class" : "className",
5154 			className : "className",
5155 			checked : "checked",
5156 			disabled : "disabled",
5157 			maxlength : "maxLength",
5158 			readonly : "readOnly",
5159 			selected : "selected",
5160 			value : "value",
5161 			id : "id",
5162 			name : "name",
5163 			type : "type"
5164 		},
5165 
5166 		DOMUtils : function(d, s) {
5167 			var t = this, globalStyle, name, blockElementsMap;
5168 
5169 			t.doc = d;
5170 			t.win = window;
5171 			t.files = {};
5172 			t.cssFlicker = false;
5173 			t.counter = 0;
5174 			t.stdMode = !tinymce.isIE || d.documentMode >= 8;
5175 			t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
5176 			t.hasOuterHTML = "outerHTML" in d.createElement("a");
5177 
5178 			t.settings = s = tinymce.extend({
5179 				keep_values : false,
5180 				hex_colors : 1
5181 			}, s);
5182 			
5183 			t.schema = s.schema;
5184 			t.styles = new tinymce.html.Styles({
5185 				url_converter : s.url_converter,
5186 				url_converter_scope : s.url_converter_scope
5187 			}, s.schema);
5188 
5189 			// Fix IE6SP2 flicker and check it failed for pre SP2
5190 			if (tinymce.isIE6) {
5191 				try {
5192 					d.execCommand('BackgroundImageCache', false, true);
5193 				} catch (e) {
5194 					t.cssFlicker = true;
5195 				}
5196 			}
5197 
5198 			t.fixDoc(d);
5199 			t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event;
5200 			tinymce.addUnload(t.destroy, t);
5201 			blockElementsMap = s.schema ? s.schema.getBlockElements() : {};
5202 
5203 			t.isBlock = function(node) {
5204 				// This function is called in module pattern style since it might be executed with the wrong this scope
5205 				var type = node.nodeType;
5206 
5207 				// If it's a node then check the type and use the nodeName
5208 				if (type)
5209 					return !!(type === 1 && blockElementsMap[node.nodeName]);
5210 
5211 				return !!blockElementsMap[node];
5212 			};
5213 		},
5214 
5215 		fixDoc: function(doc) {
5216 			var settings = this.settings, name;
5217 
5218 			if (isIE && settings.schema) {
5219 				// Add missing HTML 4/5 elements to IE
5220 				('abbr article aside audio canvas ' +
5221 				'details figcaption figure footer ' +
5222 				'header hgroup mark menu meter nav ' +
5223 				'output progress section summary ' +
5224 				'time video').replace(/\w+/g, function(name) {
5225 					doc.createElement(name);
5226 				});
5227 
5228 				// Create all custom elements
5229 				for (name in settings.schema.getCustomElements()) {
5230 					doc.createElement(name);
5231 				}
5232 			}
5233 		},
5234 
5235 		clone: function(node, deep) {
5236 			var self = this, clone, doc;
5237 
5238 			// TODO: Add feature detection here in the future
5239 			if (!isIE || node.nodeType !== 1 || deep) {
5240 				return node.cloneNode(deep);
5241 			}
5242 
5243 			doc = self.doc;
5244 
5245 			// Make a HTML5 safe shallow copy
5246 			if (!deep) {
5247 				clone = doc.createElement(node.nodeName);
5248 
5249 				// Copy attribs
5250 				each(self.getAttribs(node), function(attr) {
5251 					self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
5252 				});
5253 
5254 				return clone;
5255 			}
5256 /*
5257 			// Setup HTML5 patched document fragment
5258 			if (!self.frag) {
5259 				self.frag = doc.createDocumentFragment();
5260 				self.fixDoc(self.frag);
5261 			}
5262 
5263 			// Make a deep copy by adding it to the document fragment then removing it this removed the :section
5264 			clone = doc.createElement('div');
5265 			self.frag.appendChild(clone);
5266 			clone.innerHTML = node.outerHTML;
5267 			self.frag.removeChild(clone);
5268 */
5269 			return clone.firstChild;
5270 		},
5271 
5272 		getRoot : function() {
5273 			var t = this, s = t.settings;
5274 
5275 			return (s && t.get(s.root_element)) || t.doc.body;
5276 		},
5277 
5278 		getViewPort : function(w) {
5279 			var d, b;
5280 
5281 			w = !w ? this.win : w;
5282 			d = w.document;
5283 			b = this.boxModel ? d.documentElement : d.body;
5284 
5285 			// Returns viewport size excluding scrollbars
5286 			return {
5287 				x : w.pageXOffset || b.scrollLeft,
5288 				y : w.pageYOffset || b.scrollTop,
5289 				w : w.innerWidth || b.clientWidth,
5290 				h : w.innerHeight || b.clientHeight
5291 			};
5292 		},
5293 
5294 		getRect : function(e) {
5295 			var p, t = this, sr;
5296 
5297 			e = t.get(e);
5298 			p = t.getPos(e);
5299 			sr = t.getSize(e);
5300 
5301 			return {
5302 				x : p.x,
5303 				y : p.y,
5304 				w : sr.w,
5305 				h : sr.h
5306 			};
5307 		},
5308 
5309 		getSize : function(e) {
5310 			var t = this, w, h;
5311 
5312 			e = t.get(e);
5313 			w = t.getStyle(e, 'width');
5314 			h = t.getStyle(e, 'height');
5315 
5316 			// Non pixel value, then force offset/clientWidth
5317 			if (w.indexOf('px') === -1)
5318 				w = 0;
5319 
5320 			// Non pixel value, then force offset/clientWidth
5321 			if (h.indexOf('px') === -1)
5322 				h = 0;
5323 
5324 			return {
5325 				w : parseInt(w, 10) || e.offsetWidth || e.clientWidth,
5326 				h : parseInt(h, 10) || e.offsetHeight || e.clientHeight
5327 			};
5328 		},
5329 
5330 		getParent : function(n, f, r) {
5331 			return this.getParents(n, f, r, false);
5332 		},
5333 
5334 		getParents : function(n, f, r, c) {
5335 			var t = this, na, se = t.settings, o = [];
5336 
5337 			n = t.get(n);
5338 			c = c === undefined;
5339 
5340 			if (se.strict_root)
5341 				r = r || t.getRoot();
5342 
5343 			// Wrap node name as func
5344 			if (is(f, 'string')) {
5345 				na = f;
5346 
5347 				if (f === '*') {
5348 					f = function(n) {return n.nodeType == 1;};
5349 				} else {
5350 					f = function(n) {
5351 						return t.is(n, na);
5352 					};
5353 				}
5354 			}
5355 
5356 			while (n) {
5357 				if (n == r || !n.nodeType || n.nodeType === 9)
5358 					break;
5359 
5360 				if (!f || f(n)) {
5361 					if (c)
5362 						o.push(n);
5363 					else
5364 						return n;
5365 				}
5366 
5367 				n = n.parentNode;
5368 			}
5369 
5370 			return c ? o : null;
5371 		},
5372 
5373 		get : function(e) {
5374 			var n;
5375 
5376 			if (e && this.doc && typeof(e) == 'string') {
5377 				n = e;
5378 				e = this.doc.getElementById(e);
5379 
5380 				// IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
5381 				if (e && e.id !== n)
5382 					return this.doc.getElementsByName(n)[1];
5383 			}
5384 
5385 			return e;
5386 		},
5387 
5388 		getNext : function(node, selector) {
5389 			return this._findSib(node, selector, 'nextSibling');
5390 		},
5391 
5392 		getPrev : function(node, selector) {
5393 			return this._findSib(node, selector, 'previousSibling');
5394 		},
5395 
5396 
5397 		add : function(p, n, a, h, c) {
5398 			var t = this;
5399 
5400 			return this.run(p, function(p) {
5401 				var e, k;
5402 
5403 				e = is(n, 'string') ? t.doc.createElement(n) : n;
5404 				t.setAttribs(e, a);
5405 
5406 				if (h) {
5407 					if (h.nodeType)
5408 						e.appendChild(h);
5409 					else
5410 						t.setHTML(e, h);
5411 				}
5412 
5413 				return !c ? p.appendChild(e) : e;
5414 			});
5415 		},
5416 
5417 		create : function(n, a, h) {
5418 			return this.add(this.doc.createElement(n), n, a, h, 1);
5419 		},
5420 
5421 		createHTML : function(n, a, h) {
5422 			var o = '', t = this, k;
5423 
5424 			o += '<' + n;
5425 
5426 			for (k in a) {
5427 				if (a.hasOwnProperty(k))
5428 					o += ' ' + k + '="' + t.encode(a[k]) + '"';
5429 			}
5430 
5431 			// A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
5432 			if (typeof(h) != "undefined")
5433 				return o + '>' + h + '</' + n + '>';
5434 
5435 			return o + ' />';
5436 		},
5437 
5438 		remove : function(node, keep_children) {
5439 			return this.run(node, function(node) {
5440 				var child, parent = node.parentNode;
5441 
5442 				if (!parent)
5443 					return null;
5444 
5445 				if (keep_children) {
5446 					while (child = node.firstChild) {
5447 						// IE 8 will crash if you don't remove completely empty text nodes
5448 						if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
5449 							parent.insertBefore(child, node);
5450 						else
5451 							node.removeChild(child);
5452 					}
5453 				}
5454 
5455 				return parent.removeChild(node);
5456 			});
5457 		},
5458 
5459 		setStyle : function(n, na, v) {
5460 			var t = this;
5461 
5462 			return t.run(n, function(e) {
5463 				var s, i;
5464 
5465 				s = e.style;
5466 
5467 				// Camelcase it, if needed
5468 				na = na.replace(/-(\D)/g, function(a, b){
5469 					return b.toUpperCase();
5470 				});
5471 
5472 				// Default px suffix on these
5473 				if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
5474 					v += 'px';
5475 
5476 				switch (na) {
5477 					case 'opacity':
5478 						// IE specific opacity
5479 						if (isIE) {
5480 							s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
5481 
5482 							if (!n.currentStyle || !n.currentStyle.hasLayout)
5483 								s.display = 'inline-block';
5484 						}
5485 
5486 						// Fix for older browsers
5487 						s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
5488 						break;
5489 
5490 					case 'float':
5491 						isIE ? s.styleFloat = v : s.cssFloat = v;
5492 						break;
5493 					
5494 					default:
5495 						s[na] = v || '';
5496 				}
5497 
5498 				// Force update of the style data
5499 				if (t.settings.update_styles)
5500 					t.setAttrib(e, 'data-mce-style');
5501 			});
5502 		},
5503 
5504 		getStyle : function(n, na, c) {
5505 			n = this.get(n);
5506 
5507 			if (!n)
5508 				return;
5509 
5510 			// Gecko
5511 			if (this.doc.defaultView && c) {
5512 				// Remove camelcase
5513 				na = na.replace(/[A-Z]/g, function(a){
5514 					return '-' + a;
5515 				});
5516 
5517 				try {
5518 					return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
5519 				} catch (ex) {
5520 					// Old safari might fail
5521 					return null;
5522 				}
5523 			}
5524 
5525 			// Camelcase it, if needed
5526 			na = na.replace(/-(\D)/g, function(a, b){
5527 				return b.toUpperCase();
5528 			});
5529 
5530 			if (na == 'float')
5531 				na = isIE ? 'styleFloat' : 'cssFloat';
5532 
5533 			// IE & Opera
5534 			if (n.currentStyle && c)
5535 				return n.currentStyle[na];
5536 
5537 			return n.style ? n.style[na] : undefined;
5538 		},
5539 
5540 		setStyles : function(e, o) {
5541 			var t = this, s = t.settings, ol;
5542 
5543 			ol = s.update_styles;
5544 			s.update_styles = 0;
5545 
5546 			each(o, function(v, n) {
5547 				t.setStyle(e, n, v);
5548 			});
5549 
5550 			// Update style info
5551 			s.update_styles = ol;
5552 			if (s.update_styles)
5553 				t.setAttrib(e, s.cssText);
5554 		},
5555 
5556 		removeAllAttribs: function(e) {
5557 			return this.run(e, function(e) {
5558 				var i, attrs = e.attributes;
5559 				for (i = attrs.length - 1; i >= 0; i--) {
5560 					e.removeAttributeNode(attrs.item(i));
5561 				}
5562 			});
5563 		},
5564 
5565 		setAttrib : function(e, n, v) {
5566 			var t = this;
5567 
5568 			// Whats the point
5569 			if (!e || !n)
5570 				return;
5571 
5572 			// Strict XML mode
5573 			if (t.settings.strict)
5574 				n = n.toLowerCase();
5575 
5576 			return this.run(e, function(e) {
5577 				var s = t.settings;
5578 				var originalValue = e.getAttribute(n);
5579 				if (v !== null) {
5580 					switch (n) {
5581 						case "style":
5582 							if (!is(v, 'string')) {
5583 								each(v, function(v, n) {
5584 									t.setStyle(e, n, v);
5585 								});
5586 
5587 								return;
5588 							}
5589 
5590 							// No mce_style for elements with these since they might get resized by the user
5591 							if (s.keep_values) {
5592 								if (v && !t._isRes(v))
5593 									e.setAttribute('data-mce-style', v, 2);
5594 								else
5595 									e.removeAttribute('data-mce-style', 2);
5596 							}
5597 
5598 							e.style.cssText = v;
5599 							break;
5600 
5601 						case "class":
5602 							e.className = v || ''; // Fix IE null bug
5603 							break;
5604 
5605 						case "src":
5606 						case "href":
5607 							if (s.keep_values) {
5608 								if (s.url_converter)
5609 									v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
5610 
5611 								t.setAttrib(e, 'data-mce-' + n, v, 2);
5612 							}
5613 
5614 							break;
5615 
5616 						case "shape":
5617 							e.setAttribute('data-mce-style', v);
5618 							break;
5619 					}
5620 				}
5621 				if (is(v) && v !== null && v.length !== 0)
5622 					e.setAttribute(n, '' + v, 2);
5623 				else
5624 					e.removeAttribute(n, 2);
5625 
5626 				// fire onChangeAttrib event for attributes that have changed
5627 				if (tinyMCE.activeEditor && originalValue != v) {
5628 					var ed = tinyMCE.activeEditor;
5629 					ed.onSetAttrib.dispatch(ed, e, n, v);
5630 				}
5631 			});
5632 		},
5633 
5634 		setAttribs : function(e, o) {
5635 			var t = this;
5636 
5637 			return this.run(e, function(e) {
5638 				each(o, function(v, n) {
5639 					t.setAttrib(e, n, v);
5640 				});
5641 			});
5642 		},
5643 
5644 		getAttrib : function(e, n, dv) {
5645 			var v, t = this, undef;
5646 
5647 			e = t.get(e);
5648 
5649 			if (!e || e.nodeType !== 1)
5650 				return dv === undef ? false : dv;
5651 
5652 			if (!is(dv))
5653 				dv = '';
5654 
5655 			// Try the mce variant for these
5656 			if (/^(src|href|style|coords|shape)$/.test(n)) {
5657 				v = e.getAttribute("data-mce-" + n);
5658 
5659 				if (v)
5660 					return v;
5661 			}
5662 
5663 			if (isIE && t.props[n]) {
5664 				v = e[t.props[n]];
5665 				v = v && v.nodeValue ? v.nodeValue : v;
5666 			}
5667 
5668 			if (!v)
5669 				v = e.getAttribute(n, 2);
5670 
5671 			// Check boolean attribs
5672 			if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
5673 				if (e[t.props[n]] === true && v === '')
5674 					return n;
5675 
5676 				return v ? n : '';
5677 			}
5678 
5679 			// Inner input elements will override attributes on form elements
5680 			if (e.nodeName === "FORM" && e.getAttributeNode(n))
5681 				return e.getAttributeNode(n).nodeValue;
5682 
5683 			if (n === 'style') {
5684 				v = v || e.style.cssText;
5685 
5686 				if (v) {
5687 					v = t.serializeStyle(t.parseStyle(v), e.nodeName);
5688 
5689 					if (t.settings.keep_values && !t._isRes(v))
5690 						e.setAttribute('data-mce-style', v);
5691 				}
5692 			}
5693 
5694 			// Remove Apple and WebKit stuff
5695 			if (isWebKit && n === "class" && v)
5696 				v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
5697 
5698 			// Handle IE issues
5699 			if (isIE) {
5700 				switch (n) {
5701 					case 'rowspan':
5702 					case 'colspan':
5703 						// IE returns 1 as default value
5704 						if (v === 1)
5705 							v = '';
5706 
5707 						break;
5708 
5709 					case 'size':
5710 						// IE returns +0 as default value for size
5711 						if (v === '+0' || v === 20 || v === 0)
5712 							v = '';
5713 
5714 						break;
5715 
5716 					case 'width':
5717 					case 'height':
5718 					case 'vspace':
5719 					case 'checked':
5720 					case 'disabled':
5721 					case 'readonly':
5722 						if (v === 0)
5723 							v = '';
5724 
5725 						break;
5726 
5727 					case 'hspace':
5728 						// IE returns -1 as default value
5729 						if (v === -1)
5730 							v = '';
5731 
5732 						break;
5733 
5734 					case 'maxlength':
5735 					case 'tabindex':
5736 						// IE returns default value
5737 						if (v === 32768 || v === 2147483647 || v === '32768')
5738 							v = '';
5739 
5740 						break;
5741 
5742 					case 'multiple':
5743 					case 'compact':
5744 					case 'noshade':
5745 					case 'nowrap':
5746 						if (v === 65535)
5747 							return n;
5748 
5749 						return dv;
5750 
5751 					case 'shape':
5752 						v = v.toLowerCase();
5753 						break;
5754 
5755 					default:
5756 						// IE has odd anonymous function for event attributes
5757 						if (n.indexOf('on') === 0 && v)
5758 							v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
5759 				}
5760 			}
5761 
5762 			return (v !== undef && v !== null && v !== '') ? '' + v : dv;
5763 		},
5764 
5765 		getPos : function(n, ro) {
5766 			var t = this, x = 0, y = 0, e, d = t.doc, r;
5767 
5768 			n = t.get(n);
5769 			ro = ro || d.body;
5770 
5771 			if (n) {
5772 				// Use getBoundingClientRect if it exists since it's faster than looping offset nodes
5773 				if (n.getBoundingClientRect) {
5774 					n = n.getBoundingClientRect();
5775 					e = t.boxModel ? d.documentElement : d.body;
5776 
5777 					// Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
5778 					// Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
5779 					x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
5780 					y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
5781 
5782 					return {x : x, y : y};
5783 				}
5784 
5785 				r = n;
5786 				while (r && r != ro && r.nodeType) {
5787 					x += r.offsetLeft || 0;
5788 					y += r.offsetTop || 0;
5789 					r = r.offsetParent;
5790 				}
5791 
5792 				r = n.parentNode;
5793 				while (r && r != ro && r.nodeType) {
5794 					x -= r.scrollLeft || 0;
5795 					y -= r.scrollTop || 0;
5796 					r = r.parentNode;
5797 				}
5798 			}
5799 
5800 			return {x : x, y : y};
5801 		},
5802 
5803 		parseStyle : function(st) {
5804 			return this.styles.parse(st);
5805 		},
5806 
5807 		serializeStyle : function(o, name) {
5808 			return this.styles.serialize(o, name);
5809 		},
5810 
5811 		addStyle: function(cssText) {
5812 			var doc = this.doc, head;
5813 
5814 			// Create style element if needed
5815 			styleElm = doc.getElementById('mceDefaultStyles');
5816 			if (!styleElm) {
5817 				styleElm = doc.createElement('style'),
5818 				styleElm.id = 'mceDefaultStyles';
5819 				styleElm.type = 'text/css';
5820 
5821 				head = doc.getElementsByTagName('head')[0]
5822 				if (head.firstChild) {
5823 					head.insertBefore(styleElm, head.firstChild);
5824 				} else {
5825 					head.appendChild(styleElm);
5826 				}
5827 			}
5828 
5829 			// Append style data to old or new style element
5830 			if (styleElm.styleSheet) {
5831 				styleElm.styleSheet.cssText += cssText;
5832 			} else {
5833 				styleElm.appendChild(doc.createTextNode(cssText));
5834 			}
5835 		},
5836 
5837 		loadCSS : function(u) {
5838 			var t = this, d = t.doc, head;
5839 
5840 			if (!u)
5841 				u = '';
5842 
5843 			head = d.getElementsByTagName('head')[0];
5844 
5845 			each(u.split(','), function(u) {
5846 				var link;
5847 
5848 				if (t.files[u])
5849 					return;
5850 
5851 				t.files[u] = true;
5852 				link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
5853 
5854 				// IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
5855 				// This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
5856 				// It's ugly but it seems to work fine.
5857 				if (isIE && d.documentMode && d.recalc) {
5858 					link.onload = function() {
5859 						if (d.recalc)
5860 							d.recalc();
5861 
5862 						link.onload = null;
5863 					};
5864 				}
5865 
5866 				head.appendChild(link);
5867 			});
5868 		},
5869 
5870 		addClass : function(e, c) {
5871 			return this.run(e, function(e) {
5872 				var o;
5873 
5874 				if (!c)
5875 					return 0;
5876 
5877 				if (this.hasClass(e, c))
5878 					return e.className;
5879 
5880 				o = this.removeClass(e, c);
5881 
5882 				return e.className = (o != '' ? (o + ' ') : '') + c;
5883 			});
5884 		},
5885 
5886 		removeClass : function(e, c) {
5887 			var t = this, re;
5888 
5889 			return t.run(e, function(e) {
5890 				var v;
5891 
5892 				if (t.hasClass(e, c)) {
5893 					if (!re)
5894 						re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
5895 
5896 					v = e.className.replace(re, ' ');
5897 					v = tinymce.trim(v != ' ' ? v : '');
5898 
5899 					e.className = v;
5900 
5901 					// Empty class attr
5902 					if (!v) {
5903 						e.removeAttribute('class');
5904 						e.removeAttribute('className');
5905 					}
5906 
5907 					return v;
5908 				}
5909 
5910 				return e.className;
5911 			});
5912 		},
5913 
5914 		hasClass : function(n, c) {
5915 			n = this.get(n);
5916 
5917 			if (!n || !c)
5918 				return false;
5919 
5920 			return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
5921 		},
5922 
5923 		show : function(e) {
5924 			return this.setStyle(e, 'display', 'block');
5925 		},
5926 
5927 		hide : function(e) {
5928 			return this.setStyle(e, 'display', 'none');
5929 		},
5930 
5931 		isHidden : function(e) {
5932 			e = this.get(e);
5933 
5934 			return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
5935 		},
5936 
5937 		uniqueId : function(p) {
5938 			return (!p ? 'mce_' : p) + (this.counter++);
5939 		},
5940 
5941 		setHTML : function(element, html) {
5942 			var self = this;
5943 
5944 			return self.run(element, function(element) {
5945 				if (isIE) {
5946 					// Remove all child nodes, IE keeps empty text nodes in DOM
5947 					while (element.firstChild)
5948 						element.removeChild(element.firstChild);
5949 
5950 					try {
5951 						// IE will remove comments from the beginning
5952 						// unless you padd the contents with something
5953 						element.innerHTML = '<br />' + html;
5954 						element.removeChild(element.firstChild);
5955 					} catch (ex) {
5956 						// IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
5957 						// This seems to fix this problem
5958 
5959 						// Create new div with HTML contents and a BR infront to keep comments
5960 						var newElement = self.create('div');
5961 						newElement.innerHTML = '<br />' + html;
5962 
5963 						// Add all children from div to target
5964 						each (tinymce.grep(newElement.childNodes), function(node, i) {
5965 							// Skip br element
5966 							if (i && element.canHaveHTML)
5967 								element.appendChild(node);
5968 						});
5969 					}
5970 				} else
5971 					element.innerHTML = html;
5972 
5973 				return html;
5974 			});
5975 		},
5976 
5977 		getOuterHTML : function(elm) {
5978 			var doc, self = this;
5979 
5980 			elm = self.get(elm);
5981 
5982 			if (!elm)
5983 				return null;
5984 
5985 			if (elm.nodeType === 1 && self.hasOuterHTML)
5986 				return elm.outerHTML;
5987 
5988 			doc = (elm.ownerDocument || self.doc).createElement("body");
5989 			doc.appendChild(elm.cloneNode(true));
5990 
5991 			return doc.innerHTML;
5992 		},
5993 
5994 		setOuterHTML : function(e, h, d) {
5995 			var t = this;
5996 
5997 			function setHTML(e, h, d) {
5998 				var n, tp;
5999 
6000 				tp = d.createElement("body");
6001 				tp.innerHTML = h;
6002 
6003 				n = tp.lastChild;
6004 				while (n) {
6005 					t.insertAfter(n.cloneNode(true), e);
6006 					n = n.previousSibling;
6007 				}
6008 
6009 				t.remove(e);
6010 			};
6011 
6012 			return this.run(e, function(e) {
6013 				e = t.get(e);
6014 
6015 				// Only set HTML on elements
6016 				if (e.nodeType == 1) {
6017 					d = d || e.ownerDocument || t.doc;
6018 
6019 					if (isIE) {
6020 						try {
6021 							// Try outerHTML for IE it sometimes produces an unknown runtime error
6022 							if (isIE && e.nodeType == 1)
6023 								e.outerHTML = h;
6024 							else
6025 								setHTML(e, h, d);
6026 						} catch (ex) {
6027 							// Fix for unknown runtime error
6028 							setHTML(e, h, d);
6029 						}
6030 					} else
6031 						setHTML(e, h, d);
6032 				}
6033 			});
6034 		},
6035 
6036 		decode : Entities.decode,
6037 
6038 		encode : Entities.encodeAllRaw,
6039 
6040 		insertAfter : function(node, reference_node) {
6041 			reference_node = this.get(reference_node);
6042 
6043 			return this.run(node, function(node) {
6044 				var parent, nextSibling;
6045 
6046 				parent = reference_node.parentNode;
6047 				nextSibling = reference_node.nextSibling;
6048 
6049 				if (nextSibling)
6050 					parent.insertBefore(node, nextSibling);
6051 				else
6052 					parent.appendChild(node);
6053 
6054 				return node;
6055 			});
6056 		},
6057 
6058 		replace : function(n, o, k) {
6059 			var t = this;
6060 
6061 			if (is(o, 'array'))
6062 				n = n.cloneNode(true);
6063 
6064 			return t.run(o, function(o) {
6065 				if (k) {
6066 					each(tinymce.grep(o.childNodes), function(c) {
6067 						n.appendChild(c);
6068 					});
6069 				}
6070 
6071 				return o.parentNode.replaceChild(n, o);
6072 			});
6073 		},
6074 
6075 		rename : function(elm, name) {
6076 			var t = this, newElm;
6077 
6078 			if (elm.nodeName != name.toUpperCase()) {
6079 				// Rename block element
6080 				newElm = t.create(name);
6081 
6082 				// Copy attribs to new block
6083 				each(t.getAttribs(elm), function(attr_node) {
6084 					t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
6085 				});
6086 
6087 				// Replace block
6088 				t.replace(newElm, elm, 1);
6089 			}
6090 
6091 			return newElm || elm;
6092 		},
6093 
6094 		findCommonAncestor : function(a, b) {
6095 			var ps = a, pe;
6096 
6097 			while (ps) {
6098 				pe = b;
6099 
6100 				while (pe && ps != pe)
6101 					pe = pe.parentNode;
6102 
6103 				if (ps == pe)
6104 					break;
6105 
6106 				ps = ps.parentNode;
6107 			}
6108 
6109 			if (!ps && a.ownerDocument)
6110 				return a.ownerDocument.documentElement;
6111 
6112 			return ps;
6113 		},
6114 
6115 		toHex : function(s) {
6116 			var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
6117 
6118 			function hex(s) {
6119 				s = parseInt(s, 10).toString(16);
6120 
6121 				return s.length > 1 ? s : '0' + s; // 0 -> 00
6122 			};
6123 
6124 			if (c) {
6125 				s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
6126 
6127 				return s;
6128 			}
6129 
6130 			return s;
6131 		},
6132 
6133 		getClasses : function() {
6134 			var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
6135 
6136 			if (t.classes)
6137 				return t.classes;
6138 
6139 			function addClasses(s) {
6140 				// IE style imports
6141 				each(s.imports, function(r) {
6142 					addClasses(r);
6143 				});
6144 
6145 				each(s.cssRules || s.rules, function(r) {
6146 					// Real type or fake it on IE
6147 					switch (r.type || 1) {
6148 						// Rule
6149 						case 1:
6150 							if (r.selectorText) {
6151 								each(r.selectorText.split(','), function(v) {
6152 									v = v.replace(/^\s*|\s*$|^\s\./g, "");
6153 
6154 									// Is internal or it doesn't contain a class
6155 									if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
6156 										return;
6157 
6158 									// Remove everything but class name
6159 									ov = v;
6160 									v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
6161 
6162 									// Filter classes
6163 									if (f && !(v = f(v, ov)))
6164 										return;
6165 
6166 									if (!lo[v]) {
6167 										cl.push({'class' : v});
6168 										lo[v] = 1;
6169 									}
6170 								});
6171 							}
6172 							break;
6173 
6174 						// Import
6175 						case 3:
6176 							addClasses(r.styleSheet);
6177 							break;
6178 					}
6179 				});
6180 			};
6181 
6182 			try {
6183 				each(t.doc.styleSheets, addClasses);
6184 			} catch (ex) {
6185 				// Ignore
6186 			}
6187 
6188 			if (cl.length > 0)
6189 				t.classes = cl;
6190 
6191 			return cl;
6192 		},
6193 
6194 		run : function(e, f, s) {
6195 			var t = this, o;
6196 
6197 			if (t.doc && typeof(e) === 'string')
6198 				e = t.get(e);
6199 
6200 			if (!e)
6201 				return false;
6202 
6203 			s = s || this;
6204 			if (!e.nodeType && (e.length || e.length === 0)) {
6205 				o = [];
6206 
6207 				each(e, function(e, i) {
6208 					if (e) {
6209 						if (typeof(e) == 'string')
6210 							e = t.doc.getElementById(e);
6211 
6212 						o.push(f.call(s, e, i));
6213 					}
6214 				});
6215 
6216 				return o;
6217 			}
6218 
6219 			return f.call(s, e);
6220 		},
6221 
6222 		getAttribs : function(n) {
6223 			var o;
6224 
6225 			n = this.get(n);
6226 
6227 			if (!n)
6228 				return [];
6229 
6230 			if (isIE) {
6231 				o = [];
6232 
6233 				// Object will throw exception in IE
6234 				if (n.nodeName == 'OBJECT')
6235 					return n.attributes;
6236 
6237 				// IE doesn't keep the selected attribute if you clone option elements
6238 				if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
6239 					o.push({specified : 1, nodeName : 'selected'});
6240 
6241 				// It's crazy that this is faster in IE but it's because it returns all attributes all the time
6242 				n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
6243 					o.push({specified : 1, nodeName : a});
6244 				});
6245 
6246 				return o;
6247 			}
6248 
6249 			return n.attributes;
6250 		},
6251 
6252 		isEmpty : function(node, elements) {
6253 			var self = this, i, attributes, type, walker, name, brCount = 0;
6254 
6255 			node = node.firstChild;
6256 			if (node) {
6257 				walker = new tinymce.dom.TreeWalker(node, node.parentNode);
6258 				elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
6259 
6260 				do {
6261 					type = node.nodeType;
6262 
6263 					if (type === 1) {
6264 						// Ignore bogus elements
6265 						if (node.getAttribute('data-mce-bogus'))
6266 							continue;
6267 
6268 						// Keep empty elements like <img />
6269 						name = node.nodeName.toLowerCase();
6270 						if (elements && elements[name]) {
6271 							// Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
6272 							if (name === 'br') {
6273 								brCount++;
6274 								continue;
6275 							}
6276 
6277 							return false;
6278 						}
6279 
6280 						// Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
6281 						attributes = self.getAttribs(node);
6282 						i = node.attributes.length;
6283 						while (i--) {
6284 							name = node.attributes[i].nodeName;
6285 							if (name === "name" || name === 'data-mce-bookmark')
6286 								return false;
6287 						}
6288 					}
6289 
6290 					// Keep comment nodes
6291 					if (type == 8)
6292 						return false;
6293 
6294 					// Keep non whitespace text nodes
6295 					if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
6296 						return false;
6297 				} while (node = walker.next());
6298 			}
6299 
6300 			return brCount <= 1;
6301 		},
6302 
6303 		destroy : function(s) {
6304 			var t = this;
6305 
6306 			t.win = t.doc = t.root = t.events = t.frag = null;
6307 
6308 			// Manual destroy then remove unload handler
6309 			if (!s)
6310 				tinymce.removeUnload(t.destroy);
6311 		},
6312 
6313 		createRng : function() {
6314 			var d = this.doc;
6315 
6316 			return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
6317 		},
6318 
6319 		nodeIndex : function(node, normalized) {
6320 			var idx = 0, lastNodeType, lastNode, nodeType;
6321 
6322 			if (node) {
6323 				for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
6324 					nodeType = node.nodeType;
6325 
6326 					// Normalize text nodes
6327 					if (normalized && nodeType == 3) {
6328 						if (nodeType == lastNodeType || !node.nodeValue.length)
6329 							continue;
6330 					}
6331 					idx++;
6332 					lastNodeType = nodeType;
6333 				}
6334 			}
6335 
6336 			return idx;
6337 		},
6338 
6339 		split : function(pe, e, re) {
6340 			var t = this, r = t.createRng(), bef, aft, pa;
6341 
6342 			// W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
6343 			// but we don't want that in our code since it serves no purpose for the end user
6344 			// For example if this is chopped:
6345 			//   <p>text 1<span><b>CHOP</b></span>text 2</p>
6346 			// would produce:
6347 			//   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
6348 			// this function will then trim of empty edges and produce:
6349 			//   <p>text 1</p><b>CHOP</b><p>text 2</p>
6350 			function trim(node) {
6351 				var i, children = node.childNodes, type = node.nodeType;
6352 
6353 				function surroundedBySpans(node) {
6354 					var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
6355 					var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
6356 					return previousIsSpan && nextIsSpan;
6357 				}
6358 
6359 				if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
6360 					return;
6361 
6362 				for (i = children.length - 1; i >= 0; i--)
6363 					trim(children[i]);
6364 
6365 				if (type != 9) {
6366 					// Keep non whitespace text nodes
6367 					if (type == 3 && node.nodeValue.length > 0) {
6368 						// If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
6369 						// Also keep text nodes with only spaces if surrounded by spans.
6370 						// eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
6371 						var trimmedLength = tinymce.trim(node.nodeValue).length;
6372 						if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node))
6373 							return;
6374 					} else if (type == 1) {
6375 						// If the only child is a bookmark then move it up
6376 						children = node.childNodes;
6377 						if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
6378 							node.parentNode.insertBefore(children[0], node);
6379 
6380 						// Keep non empty elements or img, hr etc
6381 						if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
6382 							return;
6383 					}
6384 
6385 					t.remove(node);
6386 				}
6387 
6388 				return node;
6389 			};
6390 
6391 			if (pe && e) {
6392 				// Get before chunk
6393 				r.setStart(pe.parentNode, t.nodeIndex(pe));
6394 				r.setEnd(e.parentNode, t.nodeIndex(e));
6395 				bef = r.extractContents();
6396 
6397 				// Get after chunk
6398 				r = t.createRng();
6399 				r.setStart(e.parentNode, t.nodeIndex(e) + 1);
6400 				r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
6401 				aft = r.extractContents();
6402 
6403 				// Insert before chunk
6404 				pa = pe.parentNode;
6405 				pa.insertBefore(trim(bef), pe);
6406 
6407 				// Insert middle chunk
6408 				if (re)
6409 				pa.replaceChild(re, e);
6410 			else
6411 				pa.insertBefore(e, pe);
6412 
6413 				// Insert after chunk
6414 				pa.insertBefore(trim(aft), pe);
6415 				t.remove(pe);
6416 
6417 				return re || e;
6418 			}
6419 		},
6420 
6421 		bind : function(target, name, func, scope) {
6422 			return this.events.add(target, name, func, scope || this);
6423 		},
6424 
6425 		unbind : function(target, name, func) {
6426 			return this.events.remove(target, name, func);
6427 		},
6428 
6429 		fire : function(target, name, evt) {
6430 			return this.events.fire(target, name, evt);
6431 		},
6432 
6433 		// Returns the content editable state of a node
6434 		getContentEditable: function(node) {
6435 			var contentEditable;
6436 
6437 			// Check type
6438 			if (node.nodeType != 1) {
6439 				return null;
6440 			}
6441 
6442 			// Check for fake content editable
6443 			contentEditable = node.getAttribute("data-mce-contenteditable");
6444 			if (contentEditable && contentEditable !== "inherit") {
6445 				return contentEditable;
6446 			}
6447 
6448 			// Check for real content editable
6449 			return node.contentEditable !== "inherit" ? node.contentEditable : null;
6450 		},
6451 
6452 
6453 		_findSib : function(node, selector, name) {
6454 			var t = this, f = selector;
6455 
6456 			if (node) {
6457 				// If expression make a function of it using is
6458 				if (is(f, 'string')) {
6459 					f = function(node) {
6460 						return t.is(node, selector);
6461 					};
6462 				}
6463 
6464 				// Loop all siblings
6465 				for (node = node[name]; node; node = node[name]) {
6466 					if (f(node))
6467 						return node;
6468 				}
6469 			}
6470 
6471 			return null;
6472 		},
6473 
6474 		_isRes : function(c) {
6475 			// Is live resizble element
6476 			return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
6477 		}
6478 
6479 		/*
6480 		walk : function(n, f, s) {
6481 			var d = this.doc, w;
6482 
6483 			if (d.createTreeWalker) {
6484 				w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
6485 
6486 				while ((n = w.nextNode()) != null)
6487 					f.call(s || this, n);
6488 			} else
6489 				tinymce.walk(n, f, 'childNodes', s);
6490 		}
6491 		*/
6492 
6493 		/*
6494 		toRGB : function(s) {
6495 			var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
6496 
6497 			if (c) {
6498 				// #FFF -> #FFFFFF
6499 				if (!is(c[3]))
6500 					c[3] = c[2] = c[1];
6501 
6502 				return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
6503 			}
6504 
6505 			return s;
6506 		}
6507 		*/
6508 	});
6509 
6510 	tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
6511 })(tinymce);
6512 
6513 (function(ns) {
6514 	// Range constructor
6515 	function Range(dom) {
6516 		var t = this,
6517 			doc = dom.doc,
6518 			EXTRACT = 0,
6519 			CLONE = 1,
6520 			DELETE = 2,
6521 			TRUE = true,
6522 			FALSE = false,
6523 			START_OFFSET = 'startOffset',
6524 			START_CONTAINER = 'startContainer',
6525 			END_CONTAINER = 'endContainer',
6526 			END_OFFSET = 'endOffset',
6527 			extend = tinymce.extend,
6528 			nodeIndex = dom.nodeIndex;
6529 
6530 		extend(t, {
6531 			// Inital states
6532 			startContainer : doc,
6533 			startOffset : 0,
6534 			endContainer : doc,
6535 			endOffset : 0,
6536 			collapsed : TRUE,
6537 			commonAncestorContainer : doc,
6538 
6539 			// Range constants
6540 			START_TO_START : 0,
6541 			START_TO_END : 1,
6542 			END_TO_END : 2,
6543 			END_TO_START : 3,
6544 
6545 			// Public methods
6546 			setStart : setStart,
6547 			setEnd : setEnd,
6548 			setStartBefore : setStartBefore,
6549 			setStartAfter : setStartAfter,
6550 			setEndBefore : setEndBefore,
6551 			setEndAfter : setEndAfter,
6552 			collapse : collapse,
6553 			selectNode : selectNode,
6554 			selectNodeContents : selectNodeContents,
6555 			compareBoundaryPoints : compareBoundaryPoints,
6556 			deleteContents : deleteContents,
6557 			extractContents : extractContents,
6558 			cloneContents : cloneContents,
6559 			insertNode : insertNode,
6560 			surroundContents : surroundContents,
6561 			cloneRange : cloneRange,
6562 			toStringIE : toStringIE
6563 		});
6564 
6565 		function createDocumentFragment() {
6566 			return doc.createDocumentFragment();
6567 		};
6568 
6569 		function setStart(n, o) {
6570 			_setEndPoint(TRUE, n, o);
6571 		};
6572 
6573 		function setEnd(n, o) {
6574 			_setEndPoint(FALSE, n, o);
6575 		};
6576 
6577 		function setStartBefore(n) {
6578 			setStart(n.parentNode, nodeIndex(n));
6579 		};
6580 
6581 		function setStartAfter(n) {
6582 			setStart(n.parentNode, nodeIndex(n) + 1);
6583 		};
6584 
6585 		function setEndBefore(n) {
6586 			setEnd(n.parentNode, nodeIndex(n));
6587 		};
6588 
6589 		function setEndAfter(n) {
6590 			setEnd(n.parentNode, nodeIndex(n) + 1);
6591 		};
6592 
6593 		function collapse(ts) {
6594 			if (ts) {
6595 				t[END_CONTAINER] = t[START_CONTAINER];
6596 				t[END_OFFSET] = t[START_OFFSET];
6597 			} else {
6598 				t[START_CONTAINER] = t[END_CONTAINER];
6599 				t[START_OFFSET] = t[END_OFFSET];
6600 			}
6601 
6602 			t.collapsed = TRUE;
6603 		};
6604 
6605 		function selectNode(n) {
6606 			setStartBefore(n);
6607 			setEndAfter(n);
6608 		};
6609 
6610 		function selectNodeContents(n) {
6611 			setStart(n, 0);
6612 			setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
6613 		};
6614 
6615 		function compareBoundaryPoints(h, r) {
6616 			var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
6617 			rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
6618 
6619 			// Check START_TO_START
6620 			if (h === 0)
6621 				return _compareBoundaryPoints(sc, so, rsc, rso);
6622 	
6623 			// Check START_TO_END
6624 			if (h === 1)
6625 				return _compareBoundaryPoints(ec, eo, rsc, rso);
6626 	
6627 			// Check END_TO_END
6628 			if (h === 2)
6629 				return _compareBoundaryPoints(ec, eo, rec, reo);
6630 	
6631 			// Check END_TO_START
6632 			if (h === 3) 
6633 				return _compareBoundaryPoints(sc, so, rec, reo);
6634 		};
6635 
6636 		function deleteContents() {
6637 			_traverse(DELETE);
6638 		};
6639 
6640 		function extractContents() {
6641 			return _traverse(EXTRACT);
6642 		};
6643 
6644 		function cloneContents() {
6645 			return _traverse(CLONE);
6646 		};
6647 
6648 		function insertNode(n) {
6649 			var startContainer = this[START_CONTAINER],
6650 				startOffset = this[START_OFFSET], nn, o;
6651 
6652 			// Node is TEXT_NODE or CDATA
6653 			if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
6654 				if (!startOffset) {
6655 					// At the start of text
6656 					startContainer.parentNode.insertBefore(n, startContainer);
6657 				} else if (startOffset >= startContainer.nodeValue.length) {
6658 					// At the end of text
6659 					dom.insertAfter(n, startContainer);
6660 				} else {
6661 					// Middle, need to split
6662 					nn = startContainer.splitText(startOffset);
6663 					startContainer.parentNode.insertBefore(n, nn);
6664 				}
6665 			} else {
6666 				// Insert element node
6667 				if (startContainer.childNodes.length > 0)
6668 					o = startContainer.childNodes[startOffset];
6669 
6670 				if (o)
6671 					startContainer.insertBefore(n, o);
6672 				else
6673 					startContainer.appendChild(n);
6674 			}
6675 		};
6676 
6677 		function surroundContents(n) {
6678 			var f = t.extractContents();
6679 
6680 			t.insertNode(n);
6681 			n.appendChild(f);
6682 			t.selectNode(n);
6683 		};
6684 
6685 		function cloneRange() {
6686 			return extend(new Range(dom), {
6687 				startContainer : t[START_CONTAINER],
6688 				startOffset : t[START_OFFSET],
6689 				endContainer : t[END_CONTAINER],
6690 				endOffset : t[END_OFFSET],
6691 				collapsed : t.collapsed,
6692 				commonAncestorContainer : t.commonAncestorContainer
6693 			});
6694 		};
6695 
6696 		// Private methods
6697 
6698 		function _getSelectedNode(container, offset) {
6699 			var child;
6700 
6701 			if (container.nodeType == 3 /* TEXT_NODE */)
6702 				return container;
6703 
6704 			if (offset < 0)
6705 				return container;
6706 
6707 			child = container.firstChild;
6708 			while (child && offset > 0) {
6709 				--offset;
6710 				child = child.nextSibling;
6711 			}
6712 
6713 			if (child)
6714 				return child;
6715 
6716 			return container;
6717 		};
6718 
6719 		function _isCollapsed() {
6720 			return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
6721 		};
6722 
6723 		function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
6724 			var c, offsetC, n, cmnRoot, childA, childB;
6725 			
6726 			// In the first case the boundary-points have the same container. A is before B
6727 			// if its offset is less than the offset of B, A is equal to B if its offset is
6728 			// equal to the offset of B, and A is after B if its offset is greater than the
6729 			// offset of B.
6730 			if (containerA == containerB) {
6731 				if (offsetA == offsetB)
6732 					return 0; // equal
6733 
6734 				if (offsetA < offsetB)
6735 					return -1; // before
6736 
6737 				return 1; // after
6738 			}
6739 
6740 			// In the second case a child node C of the container of A is an ancestor
6741 			// container of B. In this case, A is before B if the offset of A is less than or
6742 			// equal to the index of the child node C and A is after B otherwise.
6743 			c = containerB;
6744 			while (c && c.parentNode != containerA)
6745 				c = c.parentNode;
6746 
6747 			if (c) {
6748 				offsetC = 0;
6749 				n = containerA.firstChild;
6750 
6751 				while (n != c && offsetC < offsetA) {
6752 					offsetC++;
6753 					n = n.nextSibling;
6754 				}
6755 
6756 				if (offsetA <= offsetC)
6757 					return -1; // before
6758 
6759 				return 1; // after
6760 			}
6761 
6762 			// In the third case a child node C of the container of B is an ancestor container
6763 			// of A. In this case, A is before B if the index of the child node C is less than
6764 			// the offset of B and A is after B otherwise.
6765 			c = containerA;
6766 			while (c && c.parentNode != containerB) {
6767 				c = c.parentNode;
6768 			}
6769 
6770 			if (c) {
6771 				offsetC = 0;
6772 				n = containerB.firstChild;
6773 
6774 				while (n != c && offsetC < offsetB) {
6775 					offsetC++;
6776 					n = n.nextSibling;
6777 				}
6778 
6779 				if (offsetC < offsetB)
6780 					return -1; // before
6781 
6782 				return 1; // after
6783 			}
6784 
6785 			// In the fourth case, none of three other cases hold: the containers of A and B
6786 			// are siblings or descendants of sibling nodes. In this case, A is before B if
6787 			// the container of A is before the container of B in a pre-order traversal of the
6788 			// Ranges' context tree and A is after B otherwise.
6789 			cmnRoot = dom.findCommonAncestor(containerA, containerB);
6790 			childA = containerA;
6791 
6792 			while (childA && childA.parentNode != cmnRoot)
6793 				childA = childA.parentNode;
6794 
6795 			if (!childA)
6796 				childA = cmnRoot;
6797 
6798 			childB = containerB;
6799 			while (childB && childB.parentNode != cmnRoot)
6800 				childB = childB.parentNode;
6801 
6802 			if (!childB)
6803 				childB = cmnRoot;
6804 
6805 			if (childA == childB)
6806 				return 0; // equal
6807 
6808 			n = cmnRoot.firstChild;
6809 			while (n) {
6810 				if (n == childA)
6811 					return -1; // before
6812 
6813 				if (n == childB)
6814 					return 1; // after
6815 
6816 				n = n.nextSibling;
6817 			}
6818 		};
6819 
6820 		function _setEndPoint(st, n, o) {
6821 			var ec, sc;
6822 
6823 			if (st) {
6824 				t[START_CONTAINER] = n;
6825 				t[START_OFFSET] = o;
6826 			} else {
6827 				t[END_CONTAINER] = n;
6828 				t[END_OFFSET] = o;
6829 			}
6830 
6831 			// If one boundary-point of a Range is set to have a root container
6832 			// other than the current one for the Range, the Range is collapsed to
6833 			// the new position. This enforces the restriction that both boundary-
6834 			// points of a Range must have the same root container.
6835 			ec = t[END_CONTAINER];
6836 			while (ec.parentNode)
6837 				ec = ec.parentNode;
6838 
6839 			sc = t[START_CONTAINER];
6840 			while (sc.parentNode)
6841 				sc = sc.parentNode;
6842 
6843 			if (sc == ec) {
6844 				// The start position of a Range is guaranteed to never be after the
6845 				// end position. To enforce this restriction, if the start is set to
6846 				// be at a position after the end, the Range is collapsed to that
6847 				// position.
6848 				if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
6849 					t.collapse(st);
6850 			} else
6851 				t.collapse(st);
6852 
6853 			t.collapsed = _isCollapsed();
6854 			t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
6855 		};
6856 
6857 		function _traverse(how) {
6858 			var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
6859 
6860 			if (t[START_CONTAINER] == t[END_CONTAINER])
6861 				return _traverseSameContainer(how);
6862 
6863 			for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6864 				if (p == t[START_CONTAINER])
6865 					return _traverseCommonStartContainer(c, how);
6866 
6867 				++endContainerDepth;
6868 			}
6869 
6870 			for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6871 				if (p == t[END_CONTAINER])
6872 					return _traverseCommonEndContainer(c, how);
6873 
6874 				++startContainerDepth;
6875 			}
6876 
6877 			depthDiff = startContainerDepth - endContainerDepth;
6878 
6879 			startNode = t[START_CONTAINER];
6880 			while (depthDiff > 0) {
6881 				startNode = startNode.parentNode;
6882 				depthDiff--;
6883 			}
6884 
6885 			endNode = t[END_CONTAINER];
6886 			while (depthDiff < 0) {
6887 				endNode = endNode.parentNode;
6888 				depthDiff++;
6889 			}
6890 
6891 			// ascend the ancestor hierarchy until we have a common parent.
6892 			for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
6893 				startNode = sp;
6894 				endNode = ep;
6895 			}
6896 
6897 			return _traverseCommonAncestors(startNode, endNode, how);
6898 		};
6899 
6900 		 function _traverseSameContainer(how) {
6901 			var frag, s, sub, n, cnt, sibling, xferNode, start, len;
6902 
6903 			if (how != DELETE)
6904 				frag = createDocumentFragment();
6905 
6906 			// If selection is empty, just return the fragment
6907 			if (t[START_OFFSET] == t[END_OFFSET])
6908 				return frag;
6909 
6910 			// Text node needs special case handling
6911 			if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
6912 				// get the substring
6913 				s = t[START_CONTAINER].nodeValue;
6914 				sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
6915 
6916 				// set the original text node to its new value
6917 				if (how != CLONE) {
6918 					n = t[START_CONTAINER];
6919 					start = t[START_OFFSET];
6920 					len = t[END_OFFSET] - t[START_OFFSET];
6921 
6922 					if (start === 0 && len >= n.nodeValue.length - 1) {
6923 						n.parentNode.removeChild(n);
6924 					} else {
6925 						n.deleteData(start, len);
6926 					}
6927 
6928 					// Nothing is partially selected, so collapse to start point
6929 					t.collapse(TRUE);
6930 				}
6931 
6932 				if (how == DELETE)
6933 					return;
6934 
6935 				if (sub.length > 0) {
6936 					frag.appendChild(doc.createTextNode(sub));
6937 				}
6938 
6939 				return frag;
6940 			}
6941 
6942 			// Copy nodes between the start/end offsets.
6943 			n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
6944 			cnt = t[END_OFFSET] - t[START_OFFSET];
6945 
6946 			while (n && cnt > 0) {
6947 				sibling = n.nextSibling;
6948 				xferNode = _traverseFullySelected(n, how);
6949 
6950 				if (frag)
6951 					frag.appendChild( xferNode );
6952 
6953 				--cnt;
6954 				n = sibling;
6955 			}
6956 
6957 			// Nothing is partially selected, so collapse to start point
6958 			if (how != CLONE)
6959 				t.collapse(TRUE);
6960 
6961 			return frag;
6962 		};
6963 
6964 		function _traverseCommonStartContainer(endAncestor, how) {
6965 			var frag, n, endIdx, cnt, sibling, xferNode;
6966 
6967 			if (how != DELETE)
6968 				frag = createDocumentFragment();
6969 
6970 			n = _traverseRightBoundary(endAncestor, how);
6971 
6972 			if (frag)
6973 				frag.appendChild(n);
6974 
6975 			endIdx = nodeIndex(endAncestor);
6976 			cnt = endIdx - t[START_OFFSET];
6977 
6978 			if (cnt <= 0) {
6979 				// Collapse to just before the endAncestor, which
6980 				// is partially selected.
6981 				if (how != CLONE) {
6982 					t.setEndBefore(endAncestor);
6983 					t.collapse(FALSE);
6984 				}
6985 
6986 				return frag;
6987 			}
6988 
6989 			n = endAncestor.previousSibling;
6990 			while (cnt > 0) {
6991 				sibling = n.previousSibling;
6992 				xferNode = _traverseFullySelected(n, how);
6993 
6994 				if (frag)
6995 					frag.insertBefore(xferNode, frag.firstChild);
6996 
6997 				--cnt;
6998 				n = sibling;
6999 			}
7000 
7001 			// Collapse to just before the endAncestor, which
7002 			// is partially selected.
7003 			if (how != CLONE) {
7004 				t.setEndBefore(endAncestor);
7005 				t.collapse(FALSE);
7006 			}
7007 
7008 			return frag;
7009 		};
7010 
7011 		function _traverseCommonEndContainer(startAncestor, how) {
7012 			var frag, startIdx, n, cnt, sibling, xferNode;
7013 
7014 			if (how != DELETE)
7015 				frag = createDocumentFragment();
7016 
7017 			n = _traverseLeftBoundary(startAncestor, how);
7018 			if (frag)
7019 				frag.appendChild(n);
7020 
7021 			startIdx = nodeIndex(startAncestor);
7022 			++startIdx; // Because we already traversed it
7023 
7024 			cnt = t[END_OFFSET] - startIdx;
7025 			n = startAncestor.nextSibling;
7026 			while (n && cnt > 0) {
7027 				sibling = n.nextSibling;
7028 				xferNode = _traverseFullySelected(n, how);
7029 
7030 				if (frag)
7031 					frag.appendChild(xferNode);
7032 
7033 				--cnt;
7034 				n = sibling;
7035 			}
7036 
7037 			if (how != CLONE) {
7038 				t.setStartAfter(startAncestor);
7039 				t.collapse(TRUE);
7040 			}
7041 
7042 			return frag;
7043 		};
7044 
7045 		function _traverseCommonAncestors(startAncestor, endAncestor, how) {
7046 			var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
7047 
7048 			if (how != DELETE)
7049 				frag = createDocumentFragment();
7050 
7051 			n = _traverseLeftBoundary(startAncestor, how);
7052 			if (frag)
7053 				frag.appendChild(n);
7054 
7055 			commonParent = startAncestor.parentNode;
7056 			startOffset = nodeIndex(startAncestor);
7057 			endOffset = nodeIndex(endAncestor);
7058 			++startOffset;
7059 
7060 			cnt = endOffset - startOffset;
7061 			sibling = startAncestor.nextSibling;
7062 
7063 			while (cnt > 0) {
7064 				nextSibling = sibling.nextSibling;
7065 				n = _traverseFullySelected(sibling, how);
7066 
7067 				if (frag)
7068 					frag.appendChild(n);
7069 
7070 				sibling = nextSibling;
7071 				--cnt;
7072 			}
7073 
7074 			n = _traverseRightBoundary(endAncestor, how);
7075 
7076 			if (frag)
7077 				frag.appendChild(n);
7078 
7079 			if (how != CLONE) {
7080 				t.setStartAfter(startAncestor);
7081 				t.collapse(TRUE);
7082 			}
7083 
7084 			return frag;
7085 		};
7086 
7087 		function _traverseRightBoundary(root, how) {
7088 			var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
7089 
7090 			if (next == root)
7091 				return _traverseNode(next, isFullySelected, FALSE, how);
7092 
7093 			parent = next.parentNode;
7094 			clonedParent = _traverseNode(parent, FALSE, FALSE, how);
7095 
7096 			while (parent) {
7097 				while (next) {
7098 					prevSibling = next.previousSibling;
7099 					clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
7100 
7101 					if (how != DELETE)
7102 						clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
7103 
7104 					isFullySelected = TRUE;
7105 					next = prevSibling;
7106 				}
7107 
7108 				if (parent == root)
7109 					return clonedParent;
7110 
7111 				next = parent.previousSibling;
7112 				parent = parent.parentNode;
7113 
7114 				clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
7115 
7116 				if (how != DELETE)
7117 					clonedGrandParent.appendChild(clonedParent);
7118 
7119 				clonedParent = clonedGrandParent;
7120 			}
7121 		};
7122 
7123 		function _traverseLeftBoundary(root, how) {
7124 			var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
7125 
7126 			if (next == root)
7127 				return _traverseNode(next, isFullySelected, TRUE, how);
7128 
7129 			parent = next.parentNode;
7130 			clonedParent = _traverseNode(parent, FALSE, TRUE, how);
7131 
7132 			while (parent) {
7133 				while (next) {
7134 					nextSibling = next.nextSibling;
7135 					clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
7136 
7137 					if (how != DELETE)
7138 						clonedParent.appendChild(clonedChild);
7139 
7140 					isFullySelected = TRUE;
7141 					next = nextSibling;
7142 				}
7143 
7144 				if (parent == root)
7145 					return clonedParent;
7146 
7147 				next = parent.nextSibling;
7148 				parent = parent.parentNode;
7149 
7150 				clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
7151 
7152 				if (how != DELETE)
7153 					clonedGrandParent.appendChild(clonedParent);
7154 
7155 				clonedParent = clonedGrandParent;
7156 			}
7157 		};
7158 
7159 		function _traverseNode(n, isFullySelected, isLeft, how) {
7160 			var txtValue, newNodeValue, oldNodeValue, offset, newNode;
7161 
7162 			if (isFullySelected)
7163 				return _traverseFullySelected(n, how);
7164 
7165 			if (n.nodeType == 3 /* TEXT_NODE */) {
7166 				txtValue = n.nodeValue;
7167 
7168 				if (isLeft) {
7169 					offset = t[START_OFFSET];
7170 					newNodeValue = txtValue.substring(offset);
7171 					oldNodeValue = txtValue.substring(0, offset);
7172 				} else {
7173 					offset = t[END_OFFSET];
7174 					newNodeValue = txtValue.substring(0, offset);
7175 					oldNodeValue = txtValue.substring(offset);
7176 				}
7177 
7178 				if (how != CLONE)
7179 					n.nodeValue = oldNodeValue;
7180 
7181 				if (how == DELETE)
7182 					return;
7183 
7184 				newNode = dom.clone(n, FALSE);
7185 				newNode.nodeValue = newNodeValue;
7186 
7187 				return newNode;
7188 			}
7189 
7190 			if (how == DELETE)
7191 				return;
7192 
7193 			return dom.clone(n, FALSE);
7194 		};
7195 
7196 		function _traverseFullySelected(n, how) {
7197 			if (how != DELETE)
7198 				return how == CLONE ? dom.clone(n, TRUE) : n;
7199 
7200 			n.parentNode.removeChild(n);
7201 		};
7202 
7203 		function toStringIE() {
7204 			return dom.create('body', null, cloneContents()).outerText;
7205 		}
7206 		
7207 		return t;
7208 	};
7209 
7210 	ns.Range = Range;
7211 
7212 	// Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
7213 	Range.prototype.toString = function() {
7214 		return this.toStringIE();
7215 	};
7216 })(tinymce.dom);
7217 
7218 (function() {
7219 	function Selection(selection) {
7220 		var self = this, dom = selection.dom, TRUE = true, FALSE = false;
7221 
7222 		function getPosition(rng, start) {
7223 			var checkRng, startIndex = 0, endIndex, inside,
7224 				children, child, offset, index, position = -1, parent;
7225 
7226 			// Setup test range, collapse it and get the parent
7227 			checkRng = rng.duplicate();
7228 			checkRng.collapse(start);
7229 			parent = checkRng.parentElement();
7230 
7231 			// Check if the selection is within the right document
7232 			if (parent.ownerDocument !== selection.dom.doc)
7233 				return;
7234 
7235 			// IE will report non editable elements as it's parent so look for an editable one
7236 			while (parent.contentEditable === "false") {
7237 				parent = parent.parentNode;
7238 			}
7239 
7240 			// If parent doesn't have any children then return that we are inside the element
7241 			if (!parent.hasChildNodes()) {
7242 				return {node : parent, inside : 1};
7243 			}
7244 
7245 			// Setup node list and endIndex
7246 			children = parent.children;
7247 			endIndex = children.length - 1;
7248 
7249 			// Perform a binary search for the position
7250 			while (startIndex <= endIndex) {
7251 				index = Math.floor((startIndex + endIndex) / 2);
7252 
7253 				// Move selection to node and compare the ranges
7254 				child = children[index];
7255 				checkRng.moveToElementText(child);
7256 				position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
7257 
7258 				// Before/after or an exact match
7259 				if (position > 0) {
7260 					endIndex = index - 1;
7261 				} else if (position < 0) {
7262 					startIndex = index + 1;
7263 				} else {
7264 					return {node : child};
7265 				}
7266 			}
7267 
7268 			// Check if child position is before or we didn't find a position
7269 			if (position < 0) {
7270 				// No element child was found use the parent element and the offset inside that
7271 				if (!child) {
7272 					checkRng.moveToElementText(parent);
7273 					checkRng.collapse(true);
7274 					child = parent;
7275 					inside = true;
7276 				} else
7277 					checkRng.collapse(false);
7278 
7279 				// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7280 				// We need to walk char by char since rng.text or rng.htmlText will trim line endings
7281 				offset = 0;
7282 				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7283 					if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
7284 						break;
7285 					}
7286 
7287 					offset++;
7288 				}
7289 			} else {
7290 				// Child position is after the selection endpoint
7291 				checkRng.collapse(true);
7292 
7293 				// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7294 				offset = 0;
7295 				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7296 					if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
7297 						break;
7298 					}
7299 
7300 					offset++;
7301 				}
7302 			}
7303 
7304 			return {node : child, position : position, offset : offset, inside : inside};
7305 		};
7306 
7307 		// Returns a W3C DOM compatible range object by using the IE Range API
7308 		function getRange() {
7309 			var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;
7310 
7311 			// If selection is outside the current document just return an empty range
7312 			element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
7313 			if (element.ownerDocument != dom.doc)
7314 				return domRange;
7315 
7316 			collapsed = selection.isCollapsed();
7317 
7318 			// Handle control selection
7319 			if (ieRange.item) {
7320 				domRange.setStart(element.parentNode, dom.nodeIndex(element));
7321 				domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
7322 
7323 				return domRange;
7324 			}
7325 
7326 			function findEndPoint(start) {
7327 				var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
7328 
7329 				container = endPoint.node;
7330 				offset = endPoint.offset;
7331 
7332 				if (endPoint.inside && !container.hasChildNodes()) {
7333 					domRange[start ? 'setStart' : 'setEnd'](container, 0);
7334 					return;
7335 				}
7336 
7337 				if (offset === undef) {
7338 					domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
7339 					return;
7340 				}
7341 
7342 				if (endPoint.position < 0) {
7343 					sibling = endPoint.inside ? container.firstChild : container.nextSibling;
7344 
7345 					if (!sibling) {
7346 						domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
7347 						return;
7348 					}
7349 
7350 					if (!offset) {
7351 						if (sibling.nodeType == 3)
7352 							domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
7353 						else
7354 							domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
7355 
7356 						return;
7357 					}
7358 
7359 					// Find the text node and offset
7360 					while (sibling) {
7361 						nodeValue = sibling.nodeValue;
7362 						textNodeOffset += nodeValue.length;
7363 
7364 						// We are at or passed the position we where looking for
7365 						if (textNodeOffset >= offset) {
7366 							container = sibling;
7367 							textNodeOffset -= offset;
7368 							textNodeOffset = nodeValue.length - textNodeOffset;
7369 							break;
7370 						}
7371 
7372 						sibling = sibling.nextSibling;
7373 					}
7374 				} else {
7375 					// Find the text node and offset
7376 					sibling = container.previousSibling;
7377 
7378 					if (!sibling)
7379 						return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
7380 
7381 					// If there isn't any text to loop then use the first position
7382 					if (!offset) {
7383 						if (container.nodeType == 3)
7384 							domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
7385 						else
7386 							domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
7387 
7388 						return;
7389 					}
7390 
7391 					while (sibling) {
7392 						textNodeOffset += sibling.nodeValue.length;
7393 
7394 						// We are at or passed the position we where looking for
7395 						if (textNodeOffset >= offset) {
7396 							container = sibling;
7397 							textNodeOffset -= offset;
7398 							break;
7399 						}
7400 
7401 						sibling = sibling.previousSibling;
7402 					}
7403 				}
7404 
7405 				domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
7406 			};
7407 
7408 			try {
7409 				// Find start point
7410 				findEndPoint(true);
7411 
7412 				// Find end point if needed
7413 				if (!collapsed)
7414 					findEndPoint();
7415 			} catch (ex) {
7416 				// IE has a nasty bug where text nodes might throw "invalid argument" when you
7417 				// access the nodeValue or other properties of text nodes. This seems to happend when
7418 				// text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
7419 				if (ex.number == -2147024809) {
7420 					// Get the current selection
7421 					bookmark = self.getBookmark(2);
7422 
7423 					// Get start element
7424 					tmpRange = ieRange.duplicate();
7425 					tmpRange.collapse(true);
7426 					element = tmpRange.parentElement();
7427 
7428 					// Get end element
7429 					if (!collapsed) {
7430 						tmpRange = ieRange.duplicate();
7431 						tmpRange.collapse(false);
7432 						element2 = tmpRange.parentElement();
7433 						element2.innerHTML = element2.innerHTML;
7434 					}
7435 
7436 					// Remove the broken elements
7437 					element.innerHTML = element.innerHTML;
7438 
7439 					// Restore the selection
7440 					self.moveToBookmark(bookmark);
7441 
7442 					// Since the range has moved we need to re-get it
7443 					ieRange = selection.getRng();
7444 
7445 					// Find start point
7446 					findEndPoint(true);
7447 
7448 					// Find end point if needed
7449 					if (!collapsed)
7450 						findEndPoint();
7451 				} else
7452 					throw ex; // Throw other errors
7453 			}
7454 
7455 			return domRange;
7456 		};
7457 
7458 		this.getBookmark = function(type) {
7459 			var rng = selection.getRng(), start, end, bookmark = {};
7460 
7461 			function getIndexes(node) {
7462 				var parent, root, children, i, indexes = [];
7463 
7464 				parent = node.parentNode;
7465 				root = dom.getRoot().parentNode;
7466 
7467 				while (parent != root && parent.nodeType !== 9) {
7468 					children = parent.children;
7469 
7470 					i = children.length;
7471 					while (i--) {
7472 						if (node === children[i]) {
7473 							indexes.push(i);
7474 							break;
7475 						}
7476 					}
7477 
7478 					node = parent;
7479 					parent = parent.parentNode;
7480 				}
7481 
7482 				return indexes;
7483 			};
7484 
7485 			function getBookmarkEndPoint(start) {
7486 				var position;
7487 
7488 				position = getPosition(rng, start);
7489 				if (position) {
7490 					return {
7491 						position : position.position,
7492 						offset : position.offset,
7493 						indexes : getIndexes(position.node),
7494 						inside : position.inside
7495 					};
7496 				}
7497 			};
7498 
7499 			// Non ubstructive bookmark
7500 			if (type === 2) {
7501 				// Handle text selection
7502 				if (!rng.item) {
7503 					bookmark.start = getBookmarkEndPoint(true);
7504 
7505 					if (!selection.isCollapsed())
7506 						bookmark.end = getBookmarkEndPoint();
7507 				} else
7508 					bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};
7509 			}
7510 
7511 			return bookmark;
7512 		};
7513 
7514 		this.moveToBookmark = function(bookmark) {
7515 			var rng, body = dom.doc.body;
7516 
7517 			function resolveIndexes(indexes) {
7518 				var node, i, idx, children;
7519 
7520 				node = dom.getRoot();
7521 				for (i = indexes.length - 1; i >= 0; i--) {
7522 					children = node.children;
7523 					idx = indexes[i];
7524 
7525 					if (idx <= children.length - 1) {
7526 						node = children[idx];
7527 					}
7528 				}
7529 
7530 				return node;
7531 			};
7532 			
7533 			function setBookmarkEndPoint(start) {
7534 				var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;
7535 
7536 				if (endPoint) {
7537 					moveLeft = endPoint.position > 0;
7538 
7539 					moveRng = body.createTextRange();
7540 					moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
7541 
7542 					offset = endPoint.offset;
7543 					if (offset !== undef) {
7544 						moveRng.collapse(endPoint.inside || moveLeft);
7545 						moveRng.moveStart('character', moveLeft ? -offset : offset);
7546 					} else
7547 						moveRng.collapse(start);
7548 
7549 					rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
7550 
7551 					if (start)
7552 						rng.collapse(true);
7553 				}
7554 			};
7555 
7556 			if (bookmark.start) {
7557 				if (bookmark.start.ctrl) {
7558 					rng = body.createControlRange();
7559 					rng.addElement(resolveIndexes(bookmark.start.indexes));
7560 					rng.select();
7561 				} else {
7562 					rng = body.createTextRange();
7563 					setBookmarkEndPoint(true);
7564 					setBookmarkEndPoint();
7565 					rng.select();
7566 				}
7567 			}
7568 		};
7569 
7570 		this.addRange = function(rng) {
7571 			var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, doc = selection.dom.doc, body = doc.body;
7572 
7573 			function setEndPoint(start) {
7574 				var container, offset, marker, tmpRng, nodes;
7575 
7576 				marker = dom.create('a');
7577 				container = start ? startContainer : endContainer;
7578 				offset = start ? startOffset : endOffset;
7579 				tmpRng = ieRng.duplicate();
7580 
7581 				if (container == doc || container == doc.documentElement) {
7582 					container = body;
7583 					offset = 0;
7584 				}
7585 
7586 				if (container.nodeType == 3) {
7587 					container.parentNode.insertBefore(marker, container);
7588 					tmpRng.moveToElementText(marker);
7589 					tmpRng.moveStart('character', offset);
7590 					dom.remove(marker);
7591 					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7592 				} else {
7593 					nodes = container.childNodes;
7594 
7595 					if (nodes.length) {
7596 						if (offset >= nodes.length) {
7597 							dom.insertAfter(marker, nodes[nodes.length - 1]);
7598 						} else {
7599 							container.insertBefore(marker, nodes[offset]);
7600 						}
7601 
7602 						tmpRng.moveToElementText(marker);
7603 					} else if (container.canHaveHTML) {
7604 						// Empty node selection for example <div>|</div>
7605 						// Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
7606 						container.innerHTML = '<span>\uFEFF</span>';
7607 						marker = container.firstChild;
7608 						tmpRng.moveToElementText(marker);
7609 						tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
7610 					}
7611 
7612 					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7613 					dom.remove(marker);
7614 				}
7615 			}
7616 
7617 			// Setup some shorter versions
7618 			startContainer = rng.startContainer;
7619 			startOffset = rng.startOffset;
7620 			endContainer = rng.endContainer;
7621 			endOffset = rng.endOffset;
7622 			ieRng = body.createTextRange();
7623 
7624 			// If single element selection then try making a control selection out of it
7625 			if (startContainer == endContainer && startContainer.nodeType == 1) {
7626 				// Trick to place the caret inside an empty block element like <p></p>
7627 				if (startOffset == endOffset && !startContainer.hasChildNodes()) {
7628 					if (startContainer.canHaveHTML) {
7629 						// Check if previous sibling is an empty block if it is then we need to render it
7630 						// IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
7631 						// Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
7632 						sibling = startContainer.previousSibling;
7633 						if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
7634 							sibling.innerHTML = '\uFEFF';
7635 						} else {
7636 							sibling = null;
7637 						}
7638 
7639 						startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>';
7640 						ieRng.moveToElementText(startContainer.lastChild);
7641 						ieRng.select();
7642 						dom.doc.selection.clear();
7643 						startContainer.innerHTML = '';
7644 
7645 						if (sibling) {
7646 							sibling.innerHTML = '';
7647 						}
7648 						return;
7649 					} else {
7650 						startOffset = dom.nodeIndex(startContainer);
7651 						startContainer = startContainer.parentNode;
7652 					}
7653 				}
7654 
7655 				if (startOffset == endOffset - 1) {
7656 					try {
7657 						ctrlRng = body.createControlRange();
7658 						ctrlRng.addElement(startContainer.childNodes[startOffset]);
7659 						ctrlRng.select();
7660 						return;
7661 					} catch (ex) {
7662 						// Ignore
7663 					}
7664 				}
7665 			}
7666 
7667 			// Set start/end point of selection
7668 			setEndPoint(true);
7669 			setEndPoint();
7670 
7671 			// Select the new range and scroll it into view
7672 			ieRng.select();
7673 		};
7674 
7675 		// Expose range method
7676 		this.getRangeAt = getRange;
7677 	};
7678 
7679 	// Expose the selection object
7680 	tinymce.dom.TridentSelection = Selection;
7681 })();
7682 
7683 
7684 (function(tinymce) {
7685 	tinymce.dom.Element = function(id, settings) {
7686 		var t = this, dom, el;
7687 
7688 		t.settings = settings = settings || {};
7689 		t.id = id;
7690 		t.dom = dom = settings.dom || tinymce.DOM;
7691 
7692 		// Only IE leaks DOM references, this is a lot faster
7693 		if (!tinymce.isIE)
7694 			el = dom.get(t.id);
7695 
7696 		tinymce.each(
7697 				('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 
7698 				'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 
7699 				'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 
7700 				'isHidden,setHTML,get').split(/,/), function(k) {
7701 					t[k] = function() {
7702 						var a = [id], i;
7703 
7704 						for (i = 0; i < arguments.length; i++)
7705 							a.push(arguments[i]);
7706 
7707 						a = dom[k].apply(dom, a);
7708 						t.update(k);
7709 
7710 						return a;
7711 					};
7712 			}
7713 		);
7714 
7715 		tinymce.extend(t, {
7716 			on : function(n, f, s) {
7717 				return tinymce.dom.Event.add(t.id, n, f, s);
7718 			},
7719 
7720 			getXY : function() {
7721 				return {
7722 					x : parseInt(t.getStyle('left')),
7723 					y : parseInt(t.getStyle('top'))
7724 				};
7725 			},
7726 
7727 			getSize : function() {
7728 				var n = dom.get(t.id);
7729 
7730 				return {
7731 					w : parseInt(t.getStyle('width') || n.clientWidth),
7732 					h : parseInt(t.getStyle('height') || n.clientHeight)
7733 				};
7734 			},
7735 
7736 			moveTo : function(x, y) {
7737 				t.setStyles({left : x, top : y});
7738 			},
7739 
7740 			moveBy : function(x, y) {
7741 				var p = t.getXY();
7742 
7743 				t.moveTo(p.x + x, p.y + y);
7744 			},
7745 
7746 			resizeTo : function(w, h) {
7747 				t.setStyles({width : w, height : h});
7748 			},
7749 
7750 			resizeBy : function(w, h) {
7751 				var s = t.getSize();
7752 
7753 				t.resizeTo(s.w + w, s.h + h);
7754 			},
7755 
7756 			update : function(k) {
7757 				var b;
7758 
7759 				if (tinymce.isIE6 && settings.blocker) {
7760 					k = k || '';
7761 
7762 					// Ignore getters
7763 					if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
7764 						return;
7765 
7766 					// Remove blocker on remove
7767 					if (k == 'remove') {
7768 						dom.remove(t.blocker);
7769 						return;
7770 					}
7771 
7772 					if (!t.blocker) {
7773 						t.blocker = dom.uniqueId();
7774 						b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
7775 						dom.setStyle(b, 'opacity', 0);
7776 					} else
7777 						b = dom.get(t.blocker);
7778 
7779 					dom.setStyles(b, {
7780 						left : t.getStyle('left', 1),
7781 						top : t.getStyle('top', 1),
7782 						width : t.getStyle('width', 1),
7783 						height : t.getStyle('height', 1),
7784 						display : t.getStyle('display', 1),
7785 						zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
7786 					});
7787 				}
7788 			}
7789 		});
7790 	};
7791 })(tinymce);
7792 
7793 (function(tinymce) {
7794 	function trimNl(s) {
7795 		return s.replace(/[\n\r]+/g, '');
7796 	};
7797 
7798 	// Shorten names
7799 	var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker;
7800 
7801 	tinymce.create('tinymce.dom.Selection', {
7802 		Selection : function(dom, win, serializer, editor) {
7803 			var t = this;
7804 
7805 			t.dom = dom;
7806 			t.win = win;
7807 			t.serializer = serializer;
7808 			t.editor = editor;
7809 
7810 			// Add events
7811 			each([
7812 				'onBeforeSetContent',
7813 
7814 				'onBeforeGetContent',
7815 
7816 				'onSetContent',
7817 
7818 				'onGetContent'
7819 			], function(e) {
7820 				t[e] = new tinymce.util.Dispatcher(t);
7821 			});
7822 
7823 			// No W3C Range support
7824 			if (!t.win.getSelection)
7825 				t.tridentSel = new tinymce.dom.TridentSelection(t);
7826 
7827 			if (tinymce.isIE && dom.boxModel)
7828 				this._fixIESelection();
7829 
7830 			// Prevent leaks
7831 			tinymce.addUnload(t.destroy, t);
7832 		},
7833 
7834 		setCursorLocation: function(node, offset) {
7835 			var t = this; var r = t.dom.createRng();
7836 			r.setStart(node, offset);
7837 			r.setEnd(node, offset);
7838 			t.setRng(r);
7839 			t.collapse(false);
7840 		},
7841 		getContent : function(s) {
7842 			var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
7843 
7844 			s = s || {};
7845 			wb = wa = '';
7846 			s.get = true;
7847 			s.format = s.format || 'html';
7848 			s.forced_root_block = '';
7849 			t.onBeforeGetContent.dispatch(t, s);
7850 
7851 			if (s.format == 'text')
7852 				return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
7853 
7854 			if (r.cloneContents) {
7855 				n = r.cloneContents();
7856 
7857 				if (n)
7858 					e.appendChild(n);
7859 			} else if (is(r.item) || is(r.htmlText)) {
7860 				// IE will produce invalid markup if elements are present that
7861 				// it doesn't understand like custom elements or HTML5 elements.
7862 				// Adding a BR in front of the contents and then remoiving it seems to fix it though.
7863 				e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText);
7864 				e.removeChild(e.firstChild);
7865 			} else
7866 				e.innerHTML = r.toString();
7867 
7868 			// Keep whitespace before and after
7869 			if (/^\s/.test(e.innerHTML))
7870 				wb = ' ';
7871 
7872 			if (/\s+$/.test(e.innerHTML))
7873 				wa = ' ';
7874 
7875 			s.getInner = true;
7876 
7877 			s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
7878 			t.onGetContent.dispatch(t, s);
7879 
7880 			return s.content;
7881 		},
7882 
7883 		setContent : function(content, args) {
7884 			var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
7885 
7886 			args = args || {format : 'html'};
7887 			args.set = true;
7888 			content = args.content = content;
7889 
7890 			// Dispatch before set content event
7891 			if (!args.no_events)
7892 				self.onBeforeSetContent.dispatch(self, args);
7893 
7894 			content = args.content;
7895 
7896 			if (rng.insertNode) {
7897 				// Make caret marker since insertNode places the caret in the beginning of text after insert
7898 				content += '<span id="__caret">_</span>';
7899 
7900 				// Delete and insert new node
7901 				if (rng.startContainer == doc && rng.endContainer == doc) {
7902 					// WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
7903 					doc.body.innerHTML = content;
7904 				} else {
7905 					rng.deleteContents();
7906 
7907 					if (doc.body.childNodes.length === 0) {
7908 						doc.body.innerHTML = content;
7909 					} else {
7910 						// createContextualFragment doesn't exists in IE 9 DOMRanges
7911 						if (rng.createContextualFragment) {
7912 							rng.insertNode(rng.createContextualFragment(content));
7913 						} else {
7914 							// Fake createContextualFragment call in IE 9
7915 							frag = doc.createDocumentFragment();
7916 							temp = doc.createElement('div');
7917 
7918 							frag.appendChild(temp);
7919 							temp.outerHTML = content;
7920 
7921 							rng.insertNode(frag);
7922 						}
7923 					}
7924 				}
7925 
7926 				// Move to caret marker
7927 				caretNode = self.dom.get('__caret');
7928 
7929 				// Make sure we wrap it compleatly, Opera fails with a simple select call
7930 				rng = doc.createRange();
7931 				rng.setStartBefore(caretNode);
7932 				rng.setEndBefore(caretNode);
7933 				self.setRng(rng);
7934 
7935 				// Remove the caret position
7936 				self.dom.remove('__caret');
7937 
7938 				try {
7939 					self.setRng(rng);
7940 				} catch (ex) {
7941 					// Might fail on Opera for some odd reason
7942 				}
7943 			} else {
7944 				if (rng.item) {
7945 					// Delete content and get caret text selection
7946 					doc.execCommand('Delete', false, null);
7947 					rng = self.getRng();
7948 				}
7949 
7950 				// Explorer removes spaces from the beginning of pasted contents
7951 				if (/^\s+/.test(content)) {
7952 					rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
7953 					self.dom.remove('__mce_tmp');
7954 				} else
7955 					rng.pasteHTML(content);
7956 			}
7957 
7958 			// Dispatch set content event
7959 			if (!args.no_events)
7960 				self.onSetContent.dispatch(self, args);
7961 		},
7962 
7963 		getStart : function() {
7964 			var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
7965 
7966 			if (rng.duplicate || rng.item) {
7967 				// Control selection, return first item
7968 				if (rng.item)
7969 					return rng.item(0);
7970 
7971 				// Get start element
7972 				checkRng = rng.duplicate();
7973 				checkRng.collapse(1);
7974 				startElement = checkRng.parentElement();
7975 				if (startElement.ownerDocument !== self.dom.doc) {
7976 					startElement = self.dom.getRoot();
7977 				}
7978 
7979 				// Check if range parent is inside the start element, then return the inner parent element
7980 				// This will fix issues when a single element is selected, IE would otherwise return the wrong start element
7981 				parentElement = node = rng.parentElement();
7982 				while (node = node.parentNode) {
7983 					if (node == startElement) {
7984 						startElement = parentElement;
7985 						break;
7986 					}
7987 				}
7988 
7989 				return startElement;
7990 			} else {
7991 				startElement = rng.startContainer;
7992 
7993 				if (startElement.nodeType == 1 && startElement.hasChildNodes())
7994 					startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
7995 
7996 				if (startElement && startElement.nodeType == 3)
7997 					return startElement.parentNode;
7998 
7999 				return startElement;
8000 			}
8001 		},
8002 
8003 		getEnd : function() {
8004 			var self = this, rng = self.getRng(), endElement, endOffset;
8005 
8006 			if (rng.duplicate || rng.item) {
8007 				if (rng.item)
8008 					return rng.item(0);
8009 
8010 				rng = rng.duplicate();
8011 				rng.collapse(0);
8012 				endElement = rng.parentElement();
8013 				if (endElement.ownerDocument !== self.dom.doc) {
8014 					endElement = self.dom.getRoot();
8015 				}
8016 
8017 				if (endElement && endElement.nodeName == 'BODY')
8018 					return endElement.lastChild || endElement;
8019 
8020 				return endElement;
8021 			} else {
8022 				endElement = rng.endContainer;
8023 				endOffset = rng.endOffset;
8024 
8025 				if (endElement.nodeType == 1 && endElement.hasChildNodes())
8026 					endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
8027 
8028 				if (endElement && endElement.nodeType == 3)
8029 					return endElement.parentNode;
8030 
8031 				return endElement;
8032 			}
8033 		},
8034 
8035 		getBookmark : function(type, normalized) {
8036 			var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
8037 
8038 			function findIndex(name, element) {
8039 				var index = 0;
8040 
8041 				each(dom.select(name), function(node, i) {
8042 					if (node == element)
8043 						index = i;
8044 				});
8045 
8046 				return index;
8047 			};
8048 
8049 			function normalizeTableCellSelection(rng) {
8050 				function moveEndPoint(start) {
8051 					var container, offset, childNodes, prefix = start ? 'start' : 'end';
8052 
8053 					container = rng[prefix + 'Container'];
8054 					offset = rng[prefix + 'Offset'];
8055 
8056 					if (container.nodeType == 1 && container.nodeName == "TR") {
8057 						childNodes = container.childNodes;
8058 						container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
8059 						if (container) {
8060 							offset = start ? 0 : container.childNodes.length;
8061 							rng['set' + (start ? 'Start' : 'End')](container, offset);
8062 						}
8063 					}
8064 				};
8065 
8066 				moveEndPoint(true);
8067 				moveEndPoint();
8068 
8069 				return rng;
8070 			};
8071 
8072 			function getLocation() {
8073 				var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
8074 
8075 				function getPoint(rng, start) {
8076 					var container = rng[start ? 'startContainer' : 'endContainer'],
8077 						offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
8078 
8079 					if (container.nodeType == 3) {
8080 						if (normalized) {
8081 							for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
8082 								offset += node.nodeValue.length;
8083 						}
8084 
8085 						point.push(offset);
8086 					} else {
8087 						childNodes = container.childNodes;
8088 
8089 						if (offset >= childNodes.length && childNodes.length) {
8090 							after = 1;
8091 							offset = Math.max(0, childNodes.length - 1);
8092 						}
8093 
8094 						point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
8095 					}
8096 
8097 					for (; container && container != root; container = container.parentNode)
8098 						point.push(t.dom.nodeIndex(container, normalized));
8099 
8100 					return point;
8101 				};
8102 
8103 				bookmark.start = getPoint(rng, true);
8104 
8105 				if (!t.isCollapsed())
8106 					bookmark.end = getPoint(rng);
8107 
8108 				return bookmark;
8109 			};
8110 
8111 			if (type == 2) {
8112 				if (t.tridentSel)
8113 					return t.tridentSel.getBookmark(type);
8114 
8115 				return getLocation();
8116 			}
8117 
8118 			// Handle simple range
8119 			if (type)
8120 				return {rng : t.getRng()};
8121 
8122 			rng = t.getRng();
8123 			id = dom.uniqueId();
8124 			collapsed = tinyMCE.activeEditor.selection.isCollapsed();
8125 			styles = 'overflow:hidden;line-height:0px';
8126 
8127 			// Explorer method
8128 			if (rng.duplicate || rng.item) {
8129 				// Text selection
8130 				if (!rng.item) {
8131 					rng2 = rng.duplicate();
8132 
8133 					try {
8134 						// Insert start marker
8135 						rng.collapse();
8136 						rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
8137 
8138 						// Insert end marker
8139 						if (!collapsed) {
8140 							rng2.collapse(false);
8141 
8142 							// Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
8143 							rng.moveToElementText(rng2.parentElement());
8144 							if (rng.compareEndPoints('StartToEnd', rng2) === 0)
8145 								rng2.move('character', -1);
8146 
8147 							rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
8148 						}
8149 					} catch (ex) {
8150 						// IE might throw unspecified error so lets ignore it
8151 						return null;
8152 					}
8153 				} else {
8154 					// Control selection
8155 					element = rng.item(0);
8156 					name = element.nodeName;
8157 
8158 					return {name : name, index : findIndex(name, element)};
8159 				}
8160 			} else {
8161 				element = t.getNode();
8162 				name = element.nodeName;
8163 				if (name == 'IMG')
8164 					return {name : name, index : findIndex(name, element)};
8165 
8166 				// W3C method
8167 				rng2 = normalizeTableCellSelection(rng.cloneRange());
8168 
8169 				// Insert end marker
8170 				if (!collapsed) {
8171 					rng2.collapse(false);
8172 					rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
8173 				}
8174 
8175 				rng = normalizeTableCellSelection(rng);
8176 				rng.collapse(true);
8177 				rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
8178 			}
8179 
8180 			t.moveToBookmark({id : id, keep : 1});
8181 
8182 			return {id : id};
8183 		},
8184 
8185 		moveToBookmark : function(bookmark) {
8186 			var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
8187 
8188 			function setEndPoint(start) {
8189 				var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
8190 
8191 				if (point) {
8192 					offset = point[0];
8193 
8194 					// Find container node
8195 					for (node = root, i = point.length - 1; i >= 1; i--) {
8196 						children = node.childNodes;
8197 
8198 						if (point[i] > children.length - 1)
8199 							return;
8200 
8201 						node = children[point[i]];
8202 					}
8203 
8204 					// Move text offset to best suitable location
8205 					if (node.nodeType === 3)
8206 						offset = Math.min(point[0], node.nodeValue.length);
8207 
8208 					// Move element offset to best suitable location
8209 					if (node.nodeType === 1)
8210 						offset = Math.min(point[0], node.childNodes.length);
8211 
8212 					// Set offset within container node
8213 					if (start)
8214 						rng.setStart(node, offset);
8215 					else
8216 						rng.setEnd(node, offset);
8217 				}
8218 
8219 				return true;
8220 			};
8221 
8222 			function restoreEndPoint(suffix) {
8223 				var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
8224 
8225 				if (marker) {
8226 					node = marker.parentNode;
8227 
8228 					if (suffix == 'start') {
8229 						if (!keep) {
8230 							idx = dom.nodeIndex(marker);
8231 						} else {
8232 							node = marker.firstChild;
8233 							idx = 1;
8234 						}
8235 
8236 						startContainer = endContainer = node;
8237 						startOffset = endOffset = idx;
8238 					} else {
8239 						if (!keep) {
8240 							idx = dom.nodeIndex(marker);
8241 						} else {
8242 							node = marker.firstChild;
8243 							idx = 1;
8244 						}
8245 
8246 						endContainer = node;
8247 						endOffset = idx;
8248 					}
8249 
8250 					if (!keep) {
8251 						prev = marker.previousSibling;
8252 						next = marker.nextSibling;
8253 
8254 						// Remove all marker text nodes
8255 						each(tinymce.grep(marker.childNodes), function(node) {
8256 							if (node.nodeType == 3)
8257 								node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
8258 						});
8259 
8260 						// Remove marker but keep children if for example contents where inserted into the marker
8261 						// Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
8262 						while (marker = dom.get(bookmark.id + '_' + suffix))
8263 							dom.remove(marker, 1);
8264 
8265 						// If siblings are text nodes then merge them unless it's Opera since it some how removes the node
8266 						// and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact
8267 						if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
8268 							idx = prev.nodeValue.length;
8269 							prev.appendData(next.nodeValue);
8270 							dom.remove(next);
8271 
8272 							if (suffix == 'start') {
8273 								startContainer = endContainer = prev;
8274 								startOffset = endOffset = idx;
8275 							} else {
8276 								endContainer = prev;
8277 								endOffset = idx;
8278 							}
8279 						}
8280 					}
8281 				}
8282 			};
8283 
8284 			function addBogus(node) {
8285 				// Adds a bogus BR element for empty block elements
8286 				if (dom.isBlock(node) && !node.innerHTML && !isIE)
8287 					node.innerHTML = '<br data-mce-bogus="1" />';
8288 
8289 				return node;
8290 			};
8291 
8292 			if (bookmark) {
8293 				if (bookmark.start) {
8294 					rng = dom.createRng();
8295 					root = dom.getRoot();
8296 
8297 					if (t.tridentSel)
8298 						return t.tridentSel.moveToBookmark(bookmark);
8299 
8300 					if (setEndPoint(true) && setEndPoint()) {
8301 						t.setRng(rng);
8302 					}
8303 				} else if (bookmark.id) {
8304 					// Restore start/end points
8305 					restoreEndPoint('start');
8306 					restoreEndPoint('end');
8307 
8308 					if (startContainer) {
8309 						rng = dom.createRng();
8310 						rng.setStart(addBogus(startContainer), startOffset);
8311 						rng.setEnd(addBogus(endContainer), endOffset);
8312 						t.setRng(rng);
8313 					}
8314 				} else if (bookmark.name) {
8315 					t.select(dom.select(bookmark.name)[bookmark.index]);
8316 				} else if (bookmark.rng)
8317 					t.setRng(bookmark.rng);
8318 			}
8319 		},
8320 
8321 		select : function(node, content) {
8322 			var t = this, dom = t.dom, rng = dom.createRng(), idx;
8323 
8324 			function setPoint(node, start) {
8325 				var walker = new TreeWalker(node, node);
8326 
8327 				do {
8328 					// Text node
8329 					if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) {
8330 						if (start)
8331 							rng.setStart(node, 0);
8332 						else
8333 							rng.setEnd(node, node.nodeValue.length);
8334 
8335 						return;
8336 					}
8337 
8338 					// BR element
8339 					if (node.nodeName == 'BR') {
8340 						if (start)
8341 							rng.setStartBefore(node);
8342 						else
8343 							rng.setEndBefore(node);
8344 
8345 						return;
8346 					}
8347 				} while (node = (start ? walker.next() : walker.prev()));
8348 			};
8349 
8350 			if (node) {
8351 				idx = dom.nodeIndex(node);
8352 				rng.setStart(node.parentNode, idx);
8353 				rng.setEnd(node.parentNode, idx + 1);
8354 
8355 				// Find first/last text node or BR element
8356 				if (content) {
8357 					setPoint(node, 1);
8358 					setPoint(node);
8359 				}
8360 
8361 				t.setRng(rng);
8362 			}
8363 
8364 			return node;
8365 		},
8366 
8367 		isCollapsed : function() {
8368 			var t = this, r = t.getRng(), s = t.getSel();
8369 
8370 			if (!r || r.item)
8371 				return false;
8372 
8373 			if (r.compareEndPoints)
8374 				return r.compareEndPoints('StartToEnd', r) === 0;
8375 
8376 			return !s || r.collapsed;
8377 		},
8378 
8379 		collapse : function(to_start) {
8380 			var self = this, rng = self.getRng(), node;
8381 
8382 			// Control range on IE
8383 			if (rng.item) {
8384 				node = rng.item(0);
8385 				rng = self.win.document.body.createTextRange();
8386 				rng.moveToElementText(node);
8387 			}
8388 
8389 			rng.collapse(!!to_start);
8390 			self.setRng(rng);
8391 		},
8392 
8393 		getSel : function() {
8394 			var t = this, w = this.win;
8395 
8396 			return w.getSelection ? w.getSelection() : w.document.selection;
8397 		},
8398 
8399 		getRng : function(w3c) {
8400 			var self = this, selection, rng, elm, doc = self.win.document;
8401 
8402 			// Found tridentSel object then we need to use that one
8403 			if (w3c && self.tridentSel) {
8404 				return self.tridentSel.getRangeAt(0);
8405 			}
8406 
8407 			try {
8408 				if (selection = self.getSel()) {
8409 					rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange());
8410 				}
8411 			} catch (ex) {
8412 				// IE throws unspecified error here if TinyMCE is placed in a frame/iframe
8413 			}
8414 
8415 			// We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
8416 			if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) {
8417 				elm = doc.selection.createRange().item(0);
8418 				rng = doc.createRange();
8419 				rng.setStartBefore(elm);
8420 				rng.setEndAfter(elm);
8421 			}
8422 
8423 			// No range found then create an empty one
8424 			// This can occur when the editor is placed in a hidden container element on Gecko
8425 			// Or on IE when there was an exception
8426 			if (!rng) {
8427 				rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
8428 			}
8429 
8430 			// If range is at start of document then move it to start of body
8431 			if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
8432 				elm = self.dom.getRoot();
8433 				rng.setStart(elm, 0);
8434 				rng.setEnd(elm, 0);
8435 			}
8436 
8437 			if (self.selectedRange && self.explicitRange) {
8438 				if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) {
8439 					// Safari, Opera and Chrome only ever select text which causes the range to change.
8440 					// This lets us use the originally set range if the selection hasn't been changed by the user.
8441 					rng = self.explicitRange;
8442 				} else {
8443 					self.selectedRange = null;
8444 					self.explicitRange = null;
8445 				}
8446 			}
8447 
8448 			return rng;
8449 		},
8450 
8451 		setRng : function(r, forward) {
8452 			var s, t = this;
8453 
8454 			if (!t.tridentSel) {
8455 				s = t.getSel();
8456 
8457 				if (s) {
8458 					t.explicitRange = r;
8459 
8460 					try {
8461 						s.removeAllRanges();
8462 					} catch (ex) {
8463 						// IE9 might throw errors here don't know why
8464 					}
8465 
8466 					s.addRange(r);
8467 
8468 					// Forward is set to false and we have an extend function
8469 					if (forward === false && s.extend) {
8470 						s.collapse(r.endContainer, r.endOffset);
8471 						s.extend(r.startContainer, r.startOffset);
8472 					}
8473 
8474 					// adding range isn't always successful so we need to check range count otherwise an exception can occur
8475 					t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null;
8476 				}
8477 			} else {
8478 				// Is W3C Range
8479 				if (r.cloneRange) {
8480 					try {
8481 						t.tridentSel.addRange(r);
8482 						return;
8483 					} catch (ex) {
8484 						//IE9 throws an error here if called before selection is placed in the editor
8485 					}
8486 				}
8487 
8488 				// Is IE specific range
8489 				try {
8490 					r.select();
8491 				} catch (ex) {
8492 					// Needed for some odd IE bug #1843306
8493 				}
8494 			}
8495 		},
8496 
8497 		setNode : function(n) {
8498 			var t = this;
8499 
8500 			t.setContent(t.dom.getOuterHTML(n));
8501 
8502 			return n;
8503 		},
8504 
8505 		getNode : function() {
8506 			var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
8507 
8508 			function skipEmptyTextNodes(n, forwards) {
8509 				var orig = n;
8510 				while (n && n.nodeType === 3 && n.length === 0) {
8511 					n = forwards ? n.nextSibling : n.previousSibling;
8512 				}
8513 				return n || orig;
8514 			};
8515 
8516 			// Range maybe lost after the editor is made visible again
8517 			if (!rng)
8518 				return t.dom.getRoot();
8519 
8520 			if (rng.setStart) {
8521 				elm = rng.commonAncestorContainer;
8522 
8523 				// Handle selection a image or other control like element such as anchors
8524 				if (!rng.collapsed) {
8525 					if (rng.startContainer == rng.endContainer) {
8526 						if (rng.endOffset - rng.startOffset < 2) {
8527 							if (rng.startContainer.hasChildNodes())
8528 								elm = rng.startContainer.childNodes[rng.startOffset];
8529 						}
8530 					}
8531 
8532 					// If the anchor node is a element instead of a text node then return this element
8533 					//if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
8534 					//	return sel.anchorNode.childNodes[sel.anchorOffset];
8535 
8536 					// Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
8537 					// This happens when you double click an underlined word in FireFox.
8538 					if (start.nodeType === 3 && end.nodeType === 3) {
8539 						if (start.length === rng.startOffset) {
8540 							start = skipEmptyTextNodes(start.nextSibling, true);
8541 						} else {
8542 							start = start.parentNode;
8543 						}
8544 						if (rng.endOffset === 0) {
8545 							end = skipEmptyTextNodes(end.previousSibling, false);
8546 						} else {
8547 							end = end.parentNode;
8548 						}
8549 
8550 						if (start && start === end)
8551 							return start;
8552 					}
8553 				}
8554 
8555 				if (elm && elm.nodeType == 3)
8556 					return elm.parentNode;
8557 
8558 				return elm;
8559 			}
8560 
8561 			return rng.item ? rng.item(0) : rng.parentElement();
8562 		},
8563 
8564 		getSelectedBlocks : function(st, en) {
8565 			var t = this, dom = t.dom, sb, eb, n, bl = [];
8566 
8567 			sb = dom.getParent(st || t.getStart(), dom.isBlock);
8568 			eb = dom.getParent(en || t.getEnd(), dom.isBlock);
8569 
8570 			if (sb)
8571 				bl.push(sb);
8572 
8573 			if (sb && eb && sb != eb) {
8574 				n = sb;
8575 
8576 				var walker = new TreeWalker(sb, dom.getRoot());
8577 				while ((n = walker.next()) && n != eb) {
8578 					if (dom.isBlock(n))
8579 						bl.push(n);
8580 				}
8581 			}
8582 
8583 			if (eb && sb != eb)
8584 				bl.push(eb);
8585 
8586 			return bl;
8587 		},
8588 
8589 		isForward: function(){
8590 			var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
8591 
8592 			// No support for selection direction then always return true
8593 			if (!sel || sel.anchorNode == null || sel.focusNode == null) {
8594 				return true;
8595 			}
8596 
8597 			anchorRange = dom.createRng();
8598 			anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
8599 			anchorRange.collapse(true);
8600 
8601 			focusRange = dom.createRng();
8602 			focusRange.setStart(sel.focusNode, sel.focusOffset);
8603 			focusRange.collapse(true);
8604 
8605 			return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
8606 		},
8607 
8608 		normalize : function() {
8609 			var self = this, rng, normalized, collapsed, node, sibling;
8610 
8611 			function normalizeEndPoint(start) {
8612 				var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
8613 
8614 				function hasBrBeforeAfter(node, left) {
8615 					var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
8616 
8617 					while (node = walker[left ? 'prev' : 'next']()) {
8618 						if (node.nodeName === "BR") {
8619 							return true;
8620 						}
8621 					}
8622 				};
8623 
8624 				// Walks the dom left/right to find a suitable text node to move the endpoint into
8625 				// It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
8626 				function findTextNodeRelative(left, startNode) {
8627 					var walker, lastInlineElement;
8628 
8629 					startNode = startNode || container;
8630 					walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body);
8631 
8632 					// Walk left until we hit a text node we can move to or a block/br/img
8633 					while (node = walker[left ? 'prev' : 'next']()) {
8634 						// Found text node that has a length
8635 						if (node.nodeType === 3 && node.nodeValue.length > 0) {
8636 							container = node;
8637 							offset = left ? node.nodeValue.length : 0;
8638 							normalized = true;
8639 							return;
8640 						}
8641 
8642 						// Break if we find a block or a BR/IMG/INPUT etc
8643 						if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
8644 							return;
8645 						}
8646 
8647 						lastInlineElement = node;
8648 					}
8649 
8650 					// Only fetch the last inline element when in caret mode for now
8651 					if (collapsed && lastInlineElement) {
8652 						container = lastInlineElement;
8653 						normalized = true;
8654 						offset = 0;
8655 					}
8656 				};
8657 
8658 				container = rng[(start ? 'start' : 'end') + 'Container'];
8659 				offset = rng[(start ? 'start' : 'end') + 'Offset'];
8660 				nonEmptyElementsMap = dom.schema.getNonEmptyElements();
8661 
8662 				// If the container is a document move it to the body element
8663 				if (container.nodeType === 9) {
8664 					container = dom.getRoot();
8665 					offset = 0;
8666 				}
8667 
8668 				// If the container is body try move it into the closest text node or position
8669 				if (container === body) {
8670 					// If start is before/after a image, table etc
8671 					if (start) {
8672 						node = container.childNodes[offset > 0 ? offset - 1 : 0];
8673 						if (node) {
8674 							nodeName = node.nodeName.toLowerCase();
8675 							if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
8676 								return;
8677 							}
8678 						}
8679 					}
8680 
8681 					// Resolve the index
8682 					if (container.hasChildNodes()) {
8683 						container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
8684 						offset = 0;
8685 
8686 						// Don't walk into elements that doesn't have any child nodes like a IMG
8687 						if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
8688 							// Walk the DOM to find a text node to place the caret at or a BR
8689 							node = container;
8690 							walker = new TreeWalker(container, body);
8691 
8692 							do {
8693 								// Found a text node use that position
8694 								if (node.nodeType === 3 && node.nodeValue.length > 0) {
8695 									offset = start ? 0 : node.nodeValue.length;
8696 									container = node;
8697 									normalized = true;
8698 									break;
8699 								}
8700 
8701 								// Found a BR/IMG element that we can place the caret before
8702 								if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
8703 									offset = dom.nodeIndex(node);
8704 									container = node.parentNode;
8705 
8706 									// Put caret after image when moving the end point
8707 									if (node.nodeName ==  "IMG" && !start) {
8708 										offset++;
8709 									}
8710 
8711 									normalized = true;
8712 									break;
8713 								}
8714 							} while (node = (start ? walker.next() : walker.prev()));
8715 						}
8716 					}
8717 				}
8718 
8719 				// Lean the caret to the left if possible
8720 				if (collapsed) {
8721 					// So this: <b>x</b><i>|x</i>
8722 					// Becomes: <b>x|</b><i>x</i>
8723 					// Seems that only gecko has issues with this
8724 					if (container.nodeType === 3 && offset === 0) {
8725 						findTextNodeRelative(true);
8726 					}
8727 
8728 					// Lean left into empty inline elements when the caret is before a BR
8729 					// So this: <i><b></b><i>|<br></i>
8730 					// Becomes: <i><b>|</b><i><br></i>
8731 					// Seems that only gecko has issues with this
8732 					if (container.nodeType === 1) {
8733 						node = container.childNodes[offset];
8734 						if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
8735 							findTextNodeRelative(true, container.childNodes[offset]);
8736 						}
8737 					}
8738 				}
8739 
8740 				// Lean the start of the selection right if possible
8741 				// So this: x[<b>x]</b>
8742 				// Becomes: x<b>[x]</b>
8743 				if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
8744 					findTextNodeRelative(false);
8745 				}
8746 
8747 				// Set endpoint if it was normalized
8748 				if (normalized)
8749 					rng['set' + (start ? 'Start' : 'End')](container, offset);
8750 			};
8751 
8752 			// Normalize only on non IE browsers for now
8753 			if (tinymce.isIE)
8754 				return;
8755 			
8756 			rng = self.getRng();
8757 			collapsed = rng.collapsed;
8758 
8759 			// Normalize the end points
8760 			normalizeEndPoint(true);
8761 
8762 			if (!collapsed)
8763 				normalizeEndPoint();
8764 
8765 			// Set the selection if it was normalized
8766 			if (normalized) {
8767 				// If it was collapsed then make sure it still is
8768 				if (collapsed) {
8769 					rng.collapse(true);
8770 				}
8771 
8772 				//console.log(self.dom.dumpRng(rng));
8773 				self.setRng(rng, self.isForward());
8774 			}
8775 		},
8776 
8777 		selectorChanged: function(selector, callback) {
8778 			var self = this, currentSelectors;
8779 
8780 			if (!self.selectorChangedData) {
8781 				self.selectorChangedData = {};
8782 				currentSelectors = {};
8783 
8784 				self.editor.onNodeChange.addToTop(function(ed, cm, node) {
8785 					var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
8786 
8787 					// Check for new matching selectors
8788 					each(self.selectorChangedData, function(callbacks, selector) {
8789 						each(parents, function(node) {
8790 							if (dom.is(node, selector)) {
8791 								if (!currentSelectors[selector]) {
8792 									// Execute callbacks
8793 									each(callbacks, function(callback) {
8794 										callback(true, {node: node, selector: selector, parents: parents});
8795 									});
8796 
8797 									currentSelectors[selector] = callbacks;
8798 								}
8799 
8800 								matchedSelectors[selector] = callbacks;
8801 								return false;
8802 							}
8803 						});
8804 					});
8805 
8806 					// Check if current selectors still match
8807 					each(currentSelectors, function(callbacks, selector) {
8808 						if (!matchedSelectors[selector]) {
8809 							delete currentSelectors[selector];
8810 
8811 							each(callbacks, function(callback) {
8812 								callback(false, {node: node, selector: selector, parents: parents});
8813 							});
8814 						}
8815 					});
8816 				});
8817 			}
8818 
8819 			// Add selector listeners
8820 			if (!self.selectorChangedData[selector]) {
8821 				self.selectorChangedData[selector] = [];
8822 			}
8823 
8824 			self.selectorChangedData[selector].push(callback);
8825 
8826 			return self;
8827 		},
8828 
8829 		destroy : function(manual) {
8830 			var self = this;
8831 
8832 			self.win = null;
8833 
8834 			// Manual destroy then remove unload handler
8835 			if (!manual)
8836 				tinymce.removeUnload(self.destroy);
8837 		},
8838 
8839 		// IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
8840 		_fixIESelection : function() {
8841 			var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
8842 
8843 			// Return range from point or null if it failed
8844 			function rngFromPoint(x, y) {
8845 				var rng = body.createTextRange();
8846 
8847 				try {
8848 					rng.moveToPoint(x, y);
8849 				} catch (ex) {
8850 					// IE sometimes throws and exception, so lets just ignore it
8851 					rng = null;
8852 				}
8853 
8854 				return rng;
8855 			};
8856 
8857 			// Fires while the selection is changing
8858 			function selectionChange(e) {
8859 				var pointRng;
8860 
8861 				// Check if the button is down or not
8862 				if (e.button) {
8863 					// Create range from mouse position
8864 					pointRng = rngFromPoint(e.x, e.y);
8865 
8866 					if (pointRng) {
8867 						// Check if pointRange is before/after selection then change the endPoint
8868 						if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
8869 							pointRng.setEndPoint('StartToStart', startRng);
8870 						else
8871 							pointRng.setEndPoint('EndToEnd', startRng);
8872 
8873 						pointRng.select();
8874 					}
8875 				} else
8876 					endSelection();
8877 			}
8878 
8879 			// Removes listeners
8880 			function endSelection() {
8881 				var rng = doc.selection.createRange();
8882 
8883 				// If the range is collapsed then use the last start range
8884 				if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
8885 					startRng.select();
8886 
8887 				dom.unbind(doc, 'mouseup', endSelection);
8888 				dom.unbind(doc, 'mousemove', selectionChange);
8889 				startRng = started = 0;
8890 			};
8891 
8892 			// Make HTML element unselectable since we are going to handle selection by hand
8893 			doc.documentElement.unselectable = true;
8894 			
8895 			// Detect when user selects outside BODY
8896 			dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
8897 				if (e.target.nodeName === 'HTML') {
8898 					if (started)
8899 						endSelection();
8900 
8901 					// Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
8902 					htmlElm = doc.documentElement;
8903 					if (htmlElm.scrollHeight > htmlElm.clientHeight)
8904 						return;
8905 
8906 					started = 1;
8907 					// Setup start position
8908 					startRng = rngFromPoint(e.x, e.y);
8909 					if (startRng) {
8910 						// Listen for selection change events
8911 						dom.bind(doc, 'mouseup', endSelection);
8912 						dom.bind(doc, 'mousemove', selectionChange);
8913 
8914 						dom.win.focus();
8915 						startRng.select();
8916 					}
8917 				}
8918 			});
8919 		}
8920 	});
8921 })(tinymce);
8922 
8923 (function(tinymce) {
8924 	tinymce.dom.Serializer = function(settings, dom, schema) {
8925 		var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
8926 
8927 		// Support the old apply_source_formatting option
8928 		if (!settings.apply_source_formatting)
8929 			settings.indent = false;
8930 
8931 		// Default DOM and Schema if they are undefined
8932 		dom = dom || tinymce.DOM;
8933 		schema = schema || new tinymce.html.Schema(settings);
8934 		settings.entity_encoding = settings.entity_encoding || 'named';
8935 		settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
8936 
8937 		onPreProcess = new tinymce.util.Dispatcher(self);
8938 
8939 		onPostProcess = new tinymce.util.Dispatcher(self);
8940 
8941 		htmlParser = new tinymce.html.DomParser(settings, schema);
8942 
8943 		// Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
8944 		htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
8945 			var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
8946 
8947 			while (i--) {
8948 				node = nodes[i];
8949 
8950 				value = node.attributes.map[internalName];
8951 				if (value !== undef) {
8952 					// Set external name to internal value and remove internal
8953 					node.attr(name, value.length > 0 ? value : null);
8954 					node.attr(internalName, null);
8955 				} else {
8956 					// No internal attribute found then convert the value we have in the DOM
8957 					value = node.attributes.map[name];
8958 
8959 					if (name === "style")
8960 						value = dom.serializeStyle(dom.parseStyle(value), node.name);
8961 					else if (urlConverter)
8962 						value = urlConverter.call(urlConverterScope, value, name, node.name);
8963 
8964 					node.attr(name, value.length > 0 ? value : null);
8965 				}
8966 			}
8967 		});
8968 
8969 		// Remove internal classes mceItem<..> or mceSelected
8970 		htmlParser.addAttributeFilter('class', function(nodes, name) {
8971 			var i = nodes.length, node, value;
8972 
8973 			while (i--) {
8974 				node = nodes[i];
8975 				value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, '');
8976 				node.attr('class', value.length > 0 ? value : null);
8977 			}
8978 		});
8979 
8980 		// Remove bookmark elements
8981 		htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
8982 			var i = nodes.length, node;
8983 
8984 			while (i--) {
8985 				node = nodes[i];
8986 
8987 				if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
8988 					node.remove();
8989 			}
8990 		});
8991 
8992 		// Remove expando attributes
8993 		htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) {
8994 			var i = nodes.length;
8995 
8996 			while (i--) {
8997 				nodes[i].attr(name, null);
8998 			}
8999 		});
9000 
9001 		// Force script into CDATA sections and remove the mce- prefix also add comments around styles
9002 		htmlParser.addNodeFilter('script,style', function(nodes, name) {
9003 			var i = nodes.length, node, value;
9004 
9005 			function trim(value) {
9006 				return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
9007 						.replace(/^[\r\n]*|[\r\n]*$/g, '')
9008 						.replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
9009 						.replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
9010 			};
9011 
9012 			while (i--) {
9013 				node = nodes[i];
9014 				value = node.firstChild ? node.firstChild.value : '';
9015 
9016 				if (name === "script") {
9017 					// Remove mce- prefix from script elements
9018 					node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
9019 
9020 					if (value.length > 0)
9021 						node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
9022 				} else {
9023 					if (value.length > 0)
9024 						node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
9025 				}
9026 			}
9027 		});
9028 
9029 		// Convert comments to cdata and handle protected comments
9030 		htmlParser.addNodeFilter('#comment', function(nodes, name) {
9031 			var i = nodes.length, node;
9032 
9033 			while (i--) {
9034 				node = nodes[i];
9035 
9036 				if (node.value.indexOf('[CDATA[') === 0) {
9037 					node.name = '#cdata';
9038 					node.type = 4;
9039 					node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
9040 				} else if (node.value.indexOf('mce:protected ') === 0) {
9041 					node.name = "#text";
9042 					node.type = 3;
9043 					node.raw = true;
9044 					node.value = unescape(node.value).substr(14);
9045 				}
9046 			}
9047 		});
9048 
9049 		htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
9050 			var i = nodes.length, node;
9051 
9052 			while (i--) {
9053 				node = nodes[i];
9054 				if (node.type === 7)
9055 					node.remove();
9056 				else if (node.type === 1) {
9057 					if (name === "input" && !("type" in node.attributes.map))
9058 						node.attr('type', 'text');
9059 				}
9060 			}
9061 		});
9062 
9063 		// Fix list elements, TODO: Replace this later
9064 		if (settings.fix_list_elements) {
9065 			htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
9066 				var i = nodes.length, node, parentNode;
9067 
9068 				while (i--) {
9069 					node = nodes[i];
9070 					parentNode = node.parent;
9071 
9072 					if (parentNode.name === 'ul' || parentNode.name === 'ol') {
9073 						if (node.prev && node.prev.name === 'li') {
9074 							node.prev.append(node);
9075 						}
9076 					}
9077 				}
9078 			});
9079 		}
9080 
9081 		// Remove internal data attributes
9082 		htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
9083 			var i = nodes.length;
9084 
9085 			while (i--) {
9086 				nodes[i].attr(name, null);
9087 			}
9088 		});
9089 
9090 		// Return public methods
9091 		return {
9092 			schema : schema,
9093 
9094 			addNodeFilter : htmlParser.addNodeFilter,
9095 
9096 			addAttributeFilter : htmlParser.addAttributeFilter,
9097 
9098 			onPreProcess : onPreProcess,
9099 
9100 			onPostProcess : onPostProcess,
9101 
9102 			serialize : function(node, args) {
9103 				var impl, doc, oldDoc, htmlSerializer, content;
9104 
9105 				// Explorer won't clone contents of script and style and the
9106 				// selected index of select elements are cleared on a clone operation.
9107 				if (isIE && dom.select('script,style,select,map').length > 0) {
9108 					content = node.innerHTML;
9109 					node = node.cloneNode(false);
9110 					dom.setHTML(node, content);
9111 				} else
9112 					node = node.cloneNode(true);
9113 
9114 				// Nodes needs to be attached to something in WebKit/Opera
9115 				// Older builds of Opera crashes if you attach the node to an document created dynamically
9116 				// and since we can't feature detect a crash we need to sniff the acutal build number
9117 				// This fix will make DOM ranges and make Sizzle happy!
9118 				impl = node.ownerDocument.implementation;
9119 				if (impl.createHTMLDocument) {
9120 					// Create an empty HTML document
9121 					doc = impl.createHTMLDocument("");
9122 
9123 					// Add the element or it's children if it's a body element to the new document
9124 					each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
9125 						doc.body.appendChild(doc.importNode(node, true));
9126 					});
9127 
9128 					// Grab first child or body element for serialization
9129 					if (node.nodeName != 'BODY')
9130 						node = doc.body.firstChild;
9131 					else
9132 						node = doc.body;
9133 
9134 					// set the new document in DOMUtils so createElement etc works
9135 					oldDoc = dom.doc;
9136 					dom.doc = doc;
9137 				}
9138 
9139 				args = args || {};
9140 				args.format = args.format || 'html';
9141 
9142 				// Pre process
9143 				if (!args.no_events) {
9144 					args.node = node;
9145 					onPreProcess.dispatch(self, args);
9146 				}
9147 
9148 				// Setup serializer
9149 				htmlSerializer = new tinymce.html.Serializer(settings, schema);
9150 
9151 				// Parse and serialize HTML
9152 				args.content = htmlSerializer.serialize(
9153 					htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)
9154 				);
9155 
9156 				// Replace all BOM characters for now until we can find a better solution
9157 				if (!args.cleanup)
9158 					args.content = args.content.replace(/\uFEFF|\u200B/g, '');
9159 
9160 				// Post process
9161 				if (!args.no_events)
9162 					onPostProcess.dispatch(self, args);
9163 
9164 				// Restore the old document if it was changed
9165 				if (oldDoc)
9166 					dom.doc = oldDoc;
9167 
9168 				args.node = null;
9169 
9170 				return args.content;
9171 			},
9172 
9173 			addRules : function(rules) {
9174 				schema.addValidElements(rules);
9175 			},
9176 
9177 			setRules : function(rules) {
9178 				schema.setValidElements(rules);
9179 			}
9180 		};
9181 	};
9182 })(tinymce);
9183 (function(tinymce) {
9184 	tinymce.dom.ScriptLoader = function(settings) {
9185 		var QUEUED = 0,
9186 			LOADING = 1,
9187 			LOADED = 2,
9188 			states = {},
9189 			queue = [],
9190 			scriptLoadedCallbacks = {},
9191 			queueLoadedCallbacks = [],
9192 			loading = 0,
9193 			undef;
9194 
9195 		function loadScript(url, callback) {
9196 			var t = this, dom = tinymce.DOM, elm, uri, loc, id;
9197 
9198 			// Execute callback when script is loaded
9199 			function done() {
9200 				dom.remove(id);
9201 
9202 				if (elm)
9203 					elm.onreadystatechange = elm.onload = elm = null;
9204 
9205 				callback();
9206 			};
9207 			
9208 			function error() {
9209 				// Report the error so it's easier for people to spot loading errors
9210 				if (typeof(console) !== "undefined" && console.log)
9211 					console.log("Failed to load: " + url);
9212 
9213 				// We can't mark it as done if there is a load error since
9214 				// A) We don't want to produce 404 errors on the server and
9215 				// B) the onerror event won't fire on all browsers.
9216 				// done();
9217 			};
9218 
9219 			id = dom.uniqueId();
9220 
9221 			if (tinymce.isIE6) {
9222 				uri = new tinymce.util.URI(url);
9223 				loc = location;
9224 
9225 				// If script is from same domain and we
9226 				// use IE 6 then use XHR since it's more reliable
9227 				if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
9228 					tinymce.util.XHR.send({
9229 						url : tinymce._addVer(uri.getURI()),
9230 						success : function(content) {
9231 							// Create new temp script element
9232 							var script = dom.create('script', {
9233 								type : 'text/javascript'
9234 							});
9235 
9236 							// Evaluate script in global scope
9237 							script.text = content;
9238 							document.getElementsByTagName('head')[0].appendChild(script);
9239 							dom.remove(script);
9240 
9241 							done();
9242 						},
9243 						
9244 						error : error
9245 					});
9246 
9247 					return;
9248 				}
9249 			}
9250 
9251 			// Create new script element
9252 			elm = document.createElement('script');
9253 			elm.id = id;
9254 			elm.type = 'text/javascript';
9255 			elm.src = tinymce._addVer(url);
9256 
9257 			// Add onload listener for non IE browsers since IE9
9258 			// fires onload event before the script is parsed and executed
9259 			if (!tinymce.isIE)
9260 				elm.onload = done;
9261 
9262 			// Add onerror event will get fired on some browsers but not all of them
9263 			elm.onerror = error;
9264 
9265 			// Opera 9.60 doesn't seem to fire the onreadystate event at correctly
9266 			if (!tinymce.isOpera) {
9267 				elm.onreadystatechange = function() {
9268 					var state = elm.readyState;
9269 
9270 					// Loaded state is passed on IE 6 however there
9271 					// are known issues with this method but we can't use
9272 					// XHR in a cross domain loading
9273 					if (state == 'complete' || state == 'loaded')
9274 						done();
9275 				};
9276 			}
9277 
9278 			// Most browsers support this feature so we report errors
9279 			// for those at least to help users track their missing plugins etc
9280 			// todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
9281 			/*elm.onerror = function() {
9282 				alert('Failed to load: ' + url);
9283 			};*/
9284 
9285 			// Add script to document
9286 			(document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
9287 		};
9288 
9289 		this.isDone = function(url) {
9290 			return states[url] == LOADED;
9291 		};
9292 
9293 		this.markDone = function(url) {
9294 			states[url] = LOADED;
9295 		};
9296 
9297 		this.add = this.load = function(url, callback, scope) {
9298 			var item, state = states[url];
9299 
9300 			// Add url to load queue
9301 			if (state == undef) {
9302 				queue.push(url);
9303 				states[url] = QUEUED;
9304 			}
9305 
9306 			if (callback) {
9307 				// Store away callback for later execution
9308 				if (!scriptLoadedCallbacks[url])
9309 					scriptLoadedCallbacks[url] = [];
9310 
9311 				scriptLoadedCallbacks[url].push({
9312 					func : callback,
9313 					scope : scope || this
9314 				});
9315 			}
9316 		};
9317 
9318 		this.loadQueue = function(callback, scope) {
9319 			this.loadScripts(queue, callback, scope);
9320 		};
9321 
9322 		this.loadScripts = function(scripts, callback, scope) {
9323 			var loadScripts;
9324 
9325 			function execScriptLoadedCallbacks(url) {
9326 				// Execute URL callback functions
9327 				tinymce.each(scriptLoadedCallbacks[url], function(callback) {
9328 					callback.func.call(callback.scope);
9329 				});
9330 
9331 				scriptLoadedCallbacks[url] = undef;
9332 			};
9333 
9334 			queueLoadedCallbacks.push({
9335 				func : callback,
9336 				scope : scope || this
9337 			});
9338 
9339 			loadScripts = function() {
9340 				var loadingScripts = tinymce.grep(scripts);
9341 
9342 				// Current scripts has been handled
9343 				scripts.length = 0;
9344 
9345 				// Load scripts that needs to be loaded
9346 				tinymce.each(loadingScripts, function(url) {
9347 					// Script is already loaded then execute script callbacks directly
9348 					if (states[url] == LOADED) {
9349 						execScriptLoadedCallbacks(url);
9350 						return;
9351 					}
9352 
9353 					// Is script not loading then start loading it
9354 					if (states[url] != LOADING) {
9355 						states[url] = LOADING;
9356 						loading++;
9357 
9358 						loadScript(url, function() {
9359 							states[url] = LOADED;
9360 							loading--;
9361 
9362 							execScriptLoadedCallbacks(url);
9363 
9364 							// Load more scripts if they where added by the recently loaded script
9365 							loadScripts();
9366 						});
9367 					}
9368 				});
9369 
9370 				// No scripts are currently loading then execute all pending queue loaded callbacks
9371 				if (!loading) {
9372 					tinymce.each(queueLoadedCallbacks, function(callback) {
9373 						callback.func.call(callback.scope);
9374 					});
9375 
9376 					queueLoadedCallbacks.length = 0;
9377 				}
9378 			};
9379 
9380 			loadScripts();
9381 		};
9382 	};
9383 
9384 	// Global script loader
9385 	tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
9386 })(tinymce);
9387 
9388 (function(tinymce) {
9389 	tinymce.dom.RangeUtils = function(dom) {
9390 		var INVISIBLE_CHAR = '\uFEFF';
9391 
9392 		this.walk = function(rng, callback) {
9393 			var startContainer = rng.startContainer,
9394 				startOffset = rng.startOffset,
9395 				endContainer = rng.endContainer,
9396 				endOffset = rng.endOffset,
9397 				ancestor, startPoint,
9398 				endPoint, node, parent, siblings, nodes;
9399 
9400 			// Handle table cell selection the table plugin enables
9401 			// you to fake select table cells and perform formatting actions on them
9402 			nodes = dom.select('td.mceSelected,th.mceSelected');
9403 			if (nodes.length > 0) {
9404 				tinymce.each(nodes, function(node) {
9405 					callback([node]);
9406 				});
9407 
9408 				return;
9409 			}
9410 
9411 			function exclude(nodes) {
9412 				var node;
9413 
9414 				// First node is excluded
9415 				node = nodes[0];
9416 				if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
9417 					nodes.splice(0, 1);
9418 				}
9419 
9420 				// Last node is excluded
9421 				node = nodes[nodes.length - 1];
9422 				if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
9423 					nodes.splice(nodes.length - 1, 1);
9424 				}
9425 
9426 				return nodes;
9427 			};
9428 
9429 			function collectSiblings(node, name, end_node) {
9430 				var siblings = [];
9431 
9432 				for (; node && node != end_node; node = node[name])
9433 					siblings.push(node);
9434 
9435 				return siblings;
9436 			};
9437 
9438 			function findEndPoint(node, root) {
9439 				do {
9440 					if (node.parentNode == root)
9441 						return node;
9442 
9443 					node = node.parentNode;
9444 				} while(node);
9445 			};
9446 
9447 			function walkBoundary(start_node, end_node, next) {
9448 				var siblingName = next ? 'nextSibling' : 'previousSibling';
9449 
9450 				for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
9451 					parent = node.parentNode;
9452 					siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
9453 
9454 					if (siblings.length) {
9455 						if (!next)
9456 							siblings.reverse();
9457 
9458 						callback(exclude(siblings));
9459 					}
9460 				}
9461 			};
9462 
9463 			// If index based start position then resolve it
9464 			if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
9465 				startContainer = startContainer.childNodes[startOffset];
9466 
9467 			// If index based end position then resolve it
9468 			if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
9469 				endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
9470 
9471 			// Same container
9472 			if (startContainer == endContainer)
9473 				return callback(exclude([startContainer]));
9474 
9475 			// Find common ancestor and end points
9476 			ancestor = dom.findCommonAncestor(startContainer, endContainer);
9477 				
9478 			// Process left side
9479 			for (node = startContainer; node; node = node.parentNode) {
9480 				if (node === endContainer)
9481 					return walkBoundary(startContainer, ancestor, true);
9482 
9483 				if (node === ancestor)
9484 					break;
9485 			}
9486 
9487 			// Process right side
9488 			for (node = endContainer; node; node = node.parentNode) {
9489 				if (node === startContainer)
9490 					return walkBoundary(endContainer, ancestor);
9491 
9492 				if (node === ancestor)
9493 					break;
9494 			}
9495 
9496 			// Find start/end point
9497 			startPoint = findEndPoint(startContainer, ancestor) || startContainer;
9498 			endPoint = findEndPoint(endContainer, ancestor) || endContainer;
9499 
9500 			// Walk left leaf
9501 			walkBoundary(startContainer, startPoint, true);
9502 
9503 			// Walk the middle from start to end point
9504 			siblings = collectSiblings(
9505 				startPoint == startContainer ? startPoint : startPoint.nextSibling,
9506 				'nextSibling',
9507 				endPoint == endContainer ? endPoint.nextSibling : endPoint
9508 			);
9509 
9510 			if (siblings.length)
9511 				callback(exclude(siblings));
9512 
9513 			// Walk right leaf
9514 			walkBoundary(endContainer, endPoint);
9515 		};
9516 
9517 		this.split = function(rng) {
9518 			var startContainer = rng.startContainer,
9519 				startOffset = rng.startOffset,
9520 				endContainer = rng.endContainer,
9521 				endOffset = rng.endOffset;
9522 
9523 			function splitText(node, offset) {
9524 				return node.splitText(offset);
9525 			};
9526 
9527 			// Handle single text node
9528 			if (startContainer == endContainer && startContainer.nodeType == 3) {
9529 				if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
9530 					endContainer = splitText(startContainer, startOffset);
9531 					startContainer = endContainer.previousSibling;
9532 
9533 					if (endOffset > startOffset) {
9534 						endOffset = endOffset - startOffset;
9535 						startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
9536 						endOffset = endContainer.nodeValue.length;
9537 						startOffset = 0;
9538 					} else {
9539 						endOffset = 0;
9540 					}
9541 				}
9542 			} else {
9543 				// Split startContainer text node if needed
9544 				if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
9545 					startContainer = splitText(startContainer, startOffset);
9546 					startOffset = 0;
9547 				}
9548 
9549 				// Split endContainer text node if needed
9550 				if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
9551 					endContainer = splitText(endContainer, endOffset).previousSibling;
9552 					endOffset = endContainer.nodeValue.length;
9553 				}
9554 			}
9555 
9556 			return {
9557 				startContainer : startContainer,
9558 				startOffset : startOffset,
9559 				endContainer : endContainer,
9560 				endOffset : endOffset
9561 			};
9562 		};
9563 
9564 	};
9565 
9566 	tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
9567 		if (rng1 && rng2) {
9568 			// Compare native IE ranges
9569 			if (rng1.item || rng1.duplicate) {
9570 				// Both are control ranges and the selected element matches
9571 				if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
9572 					return true;
9573 
9574 				// Both are text ranges and the range matches
9575 				if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
9576 					return true;
9577 			} else {
9578 				// Compare w3c ranges
9579 				return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
9580 			}
9581 		}
9582 
9583 		return false;
9584 	};
9585 })(tinymce);
9586 
9587 (function(tinymce) {
9588 	var Event = tinymce.dom.Event, each = tinymce.each;
9589 
9590 	tinymce.create('tinymce.ui.KeyboardNavigation', {
9591 		KeyboardNavigation: function(settings, dom) {
9592 			var t = this, root = settings.root, items = settings.items,
9593 					enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
9594 					excludeFromTabOrder = settings.excludeFromTabOrder,
9595 					itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
9596 
9597 			dom = dom || tinymce.DOM;
9598 
9599 			itemFocussed = function(evt) {
9600 				focussedId = evt.target.id;
9601 			};
9602 			
9603 			itemBlurred = function(evt) {
9604 				dom.setAttrib(evt.target.id, 'tabindex', '-1');
9605 			};
9606 			
9607 			rootFocussed = function(evt) {
9608 				var item = dom.get(focussedId);
9609 				dom.setAttrib(item, 'tabindex', '0');
9610 				item.focus();
9611 			};
9612 			
9613 			t.focus = function() {
9614 				dom.get(focussedId).focus();
9615 			};
9616 
9617 			t.destroy = function() {
9618 				each(items, function(item) {
9619 					var elm = dom.get(item.id);
9620 
9621 					dom.unbind(elm, 'focus', itemFocussed);
9622 					dom.unbind(elm, 'blur', itemBlurred);
9623 				});
9624 
9625 				var rootElm = dom.get(root);
9626 				dom.unbind(rootElm, 'focus', rootFocussed);
9627 				dom.unbind(rootElm, 'keydown', rootKeydown);
9628 
9629 				items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
9630 				t.destroy = function() {};
9631 			};
9632 			
9633 			t.moveFocus = function(dir, evt) {
9634 				var idx = -1, controls = t.controls, newFocus;
9635 
9636 				if (!focussedId)
9637 					return;
9638 
9639 				each(items, function(item, index) {
9640 					if (item.id === focussedId) {
9641 						idx = index;
9642 						return false;
9643 					}
9644 				});
9645 
9646 				idx += dir;
9647 				if (idx < 0) {
9648 					idx = items.length - 1;
9649 				} else if (idx >= items.length) {
9650 					idx = 0;
9651 				}
9652 				
9653 				newFocus = items[idx];
9654 				dom.setAttrib(focussedId, 'tabindex', '-1');
9655 				dom.setAttrib(newFocus.id, 'tabindex', '0');
9656 				dom.get(newFocus.id).focus();
9657 
9658 				if (settings.actOnFocus) {
9659 					settings.onAction(newFocus.id);
9660 				}
9661 
9662 				if (evt)
9663 					Event.cancel(evt);
9664 			};
9665 			
9666 			rootKeydown = function(evt) {
9667 				var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
9668 				
9669 				switch (evt.keyCode) {
9670 					case DOM_VK_LEFT:
9671 						if (enableLeftRight) t.moveFocus(-1);
9672 						break;
9673 	
9674 					case DOM_VK_RIGHT:
9675 						if (enableLeftRight) t.moveFocus(1);
9676 						break;
9677 	
9678 					case DOM_VK_UP:
9679 						if (enableUpDown) t.moveFocus(-1);
9680 						break;
9681 
9682 					case DOM_VK_DOWN:
9683 						if (enableUpDown) t.moveFocus(1);
9684 						break;
9685 
9686 					case DOM_VK_ESCAPE:
9687 						if (settings.onCancel) {
9688 							settings.onCancel();
9689 							Event.cancel(evt);
9690 						}
9691 						break;
9692 
9693 					case DOM_VK_ENTER:
9694 					case DOM_VK_RETURN:
9695 					case DOM_VK_SPACE:
9696 						if (settings.onAction) {
9697 							settings.onAction(focussedId);
9698 							Event.cancel(evt);
9699 						}
9700 						break;
9701 				}
9702 			};
9703 
9704 			// Set up state and listeners for each item.
9705 			each(items, function(item, idx) {
9706 				var tabindex, elm;
9707 
9708 				if (!item.id) {
9709 					item.id = dom.uniqueId('_mce_item_');
9710 				}
9711 
9712 				elm = dom.get(item.id);
9713 
9714 				if (excludeFromTabOrder) {
9715 					dom.bind(elm, 'blur', itemBlurred);
9716 					tabindex = '-1';
9717 				} else {
9718 					tabindex = (idx === 0 ? '0' : '-1');
9719 				}
9720 
9721 				elm.setAttribute('tabindex', tabindex);
9722 				dom.bind(elm, 'focus', itemFocussed);
9723 			});
9724 			
9725 			// Setup initial state for root element.
9726 			if (items[0]){
9727 				focussedId = items[0].id;
9728 			}
9729 
9730 			dom.setAttrib(root, 'tabindex', '-1');
9731 
9732 			// Setup listeners for root element.
9733 			var rootElm = dom.get(root);
9734 			dom.bind(rootElm, 'focus', rootFocussed);
9735 			dom.bind(rootElm, 'keydown', rootKeydown);
9736 		}
9737 	});
9738 })(tinymce);
9739 
9740 (function(tinymce) {
9741 	// Shorten class names
9742 	var DOM = tinymce.DOM, is = tinymce.is;
9743 
9744 	tinymce.create('tinymce.ui.Control', {
9745 		Control : function(id, s, editor) {
9746 			this.id = id;
9747 			this.settings = s = s || {};
9748 			this.rendered = false;
9749 			this.onRender = new tinymce.util.Dispatcher(this);
9750 			this.classPrefix = '';
9751 			this.scope = s.scope || this;
9752 			this.disabled = 0;
9753 			this.active = 0;
9754 			this.editor = editor;
9755 		},
9756 		
9757 		setAriaProperty : function(property, value) {
9758 			var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
9759 			if (element) {
9760 				DOM.setAttrib(element, 'aria-' + property, !!value);
9761 			}
9762 		},
9763 		
9764 		focus : function() {
9765 			DOM.get(this.id).focus();
9766 		},
9767 
9768 		setDisabled : function(s) {
9769 			if (s != this.disabled) {
9770 				this.setAriaProperty('disabled', s);
9771 
9772 				this.setState('Disabled', s);
9773 				this.setState('Enabled', !s);
9774 				this.disabled = s;
9775 			}
9776 		},
9777 
9778 		isDisabled : function() {
9779 			return this.disabled;
9780 		},
9781 
9782 		setActive : function(s) {
9783 			if (s != this.active) {
9784 				this.setState('Active', s);
9785 				this.active = s;
9786 				this.setAriaProperty('pressed', s);
9787 			}
9788 		},
9789 
9790 		isActive : function() {
9791 			return this.active;
9792 		},
9793 
9794 		setState : function(c, s) {
9795 			var n = DOM.get(this.id);
9796 
9797 			c = this.classPrefix + c;
9798 
9799 			if (s)
9800 				DOM.addClass(n, c);
9801 			else
9802 				DOM.removeClass(n, c);
9803 		},
9804 
9805 		isRendered : function() {
9806 			return this.rendered;
9807 		},
9808 
9809 		renderHTML : function() {
9810 		},
9811 
9812 		renderTo : function(n) {
9813 			DOM.setHTML(n, this.renderHTML());
9814 		},
9815 
9816 		postRender : function() {
9817 			var t = this, b;
9818 
9819 			// Set pending states
9820 			if (is(t.disabled)) {
9821 				b = t.disabled;
9822 				t.disabled = -1;
9823 				t.setDisabled(b);
9824 			}
9825 
9826 			if (is(t.active)) {
9827 				b = t.active;
9828 				t.active = -1;
9829 				t.setActive(b);
9830 			}
9831 		},
9832 
9833 		remove : function() {
9834 			DOM.remove(this.id);
9835 			this.destroy();
9836 		},
9837 
9838 		destroy : function() {
9839 			tinymce.dom.Event.clear(this.id);
9840 		}
9841 	});
9842 })(tinymce);
9843 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
9844 	Container : function(id, s, editor) {
9845 		this.parent(id, s, editor);
9846 
9847 		this.controls = [];
9848 
9849 		this.lookup = {};
9850 	},
9851 
9852 	add : function(c) {
9853 		this.lookup[c.id] = c;
9854 		this.controls.push(c);
9855 
9856 		return c;
9857 	},
9858 
9859 	get : function(n) {
9860 		return this.lookup[n];
9861 	}
9862 });
9863 
9864 
9865 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
9866 	Separator : function(id, s) {
9867 		this.parent(id, s);
9868 		this.classPrefix = 'mceSeparator';
9869 		this.setDisabled(true);
9870 	},
9871 
9872 	renderHTML : function() {
9873 		return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
9874 	}
9875 });
9876 
9877 (function(tinymce) {
9878 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
9879 
9880 	tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
9881 		MenuItem : function(id, s) {
9882 			this.parent(id, s);
9883 			this.classPrefix = 'mceMenuItem';
9884 		},
9885 
9886 		setSelected : function(s) {
9887 			this.setState('Selected', s);
9888 			this.setAriaProperty('checked', !!s);
9889 			this.selected = s;
9890 		},
9891 
9892 		isSelected : function() {
9893 			return this.selected;
9894 		},
9895 
9896 		postRender : function() {
9897 			var t = this;
9898 			
9899 			t.parent();
9900 
9901 			// Set pending state
9902 			if (is(t.selected))
9903 				t.setSelected(t.selected);
9904 		}
9905 	});
9906 })(tinymce);
9907 
9908 (function(tinymce) {
9909 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
9910 
9911 	tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
9912 		Menu : function(id, s) {
9913 			var t = this;
9914 
9915 			t.parent(id, s);
9916 			t.items = {};
9917 			t.collapsed = false;
9918 			t.menuCount = 0;
9919 			t.onAddItem = new tinymce.util.Dispatcher(this);
9920 		},
9921 
9922 		expand : function(d) {
9923 			var t = this;
9924 
9925 			if (d) {
9926 				walk(t, function(o) {
9927 					if (o.expand)
9928 						o.expand();
9929 				}, 'items', t);
9930 			}
9931 
9932 			t.collapsed = false;
9933 		},
9934 
9935 		collapse : function(d) {
9936 			var t = this;
9937 
9938 			if (d) {
9939 				walk(t, function(o) {
9940 					if (o.collapse)
9941 						o.collapse();
9942 				}, 'items', t);
9943 			}
9944 
9945 			t.collapsed = true;
9946 		},
9947 
9948 		isCollapsed : function() {
9949 			return this.collapsed;
9950 		},
9951 
9952 		add : function(o) {
9953 			if (!o.settings)
9954 				o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
9955 
9956 			this.onAddItem.dispatch(this, o);
9957 
9958 			return this.items[o.id] = o;
9959 		},
9960 
9961 		addSeparator : function() {
9962 			return this.add({separator : true});
9963 		},
9964 
9965 		addMenu : function(o) {
9966 			if (!o.collapse)
9967 				o = this.createMenu(o);
9968 
9969 			this.menuCount++;
9970 
9971 			return this.add(o);
9972 		},
9973 
9974 		hasMenus : function() {
9975 			return this.menuCount !== 0;
9976 		},
9977 
9978 		remove : function(o) {
9979 			delete this.items[o.id];
9980 		},
9981 
9982 		removeAll : function() {
9983 			var t = this;
9984 
9985 			walk(t, function(o) {
9986 				if (o.removeAll)
9987 					o.removeAll();
9988 				else
9989 					o.remove();
9990 
9991 				o.destroy();
9992 			}, 'items', t);
9993 
9994 			t.items = {};
9995 		},
9996 
9997 		createMenu : function(o) {
9998 			var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
9999 
10000 			m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
10001 
10002 			return m;
10003 		}
10004 	});
10005 })(tinymce);
10006 (function(tinymce) {
10007 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
10008 
10009 	tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
10010 		DropMenu : function(id, s) {
10011 			s = s || {};
10012 			s.container = s.container || DOM.doc.body;
10013 			s.offset_x = s.offset_x || 0;
10014 			s.offset_y = s.offset_y || 0;
10015 			s.vp_offset_x = s.vp_offset_x || 0;
10016 			s.vp_offset_y = s.vp_offset_y || 0;
10017 
10018 			if (is(s.icons) && !s.icons)
10019 				s['class'] += ' mceNoIcons';
10020 
10021 			this.parent(id, s);
10022 			this.onShowMenu = new tinymce.util.Dispatcher(this);
10023 			this.onHideMenu = new tinymce.util.Dispatcher(this);
10024 			this.classPrefix = 'mceMenu';
10025 		},
10026 
10027 		createMenu : function(s) {
10028 			var t = this, cs = t.settings, m;
10029 
10030 			s.container = s.container || cs.container;
10031 			s.parent = t;
10032 			s.constrain = s.constrain || cs.constrain;
10033 			s['class'] = s['class'] || cs['class'];
10034 			s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
10035 			s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
10036 			s.keyboard_focus = cs.keyboard_focus;
10037 			m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
10038 
10039 			m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
10040 
10041 			return m;
10042 		},
10043 		
10044 		focus : function() {
10045 			var t = this;
10046 			if (t.keyboardNav) {
10047 				t.keyboardNav.focus();
10048 			}
10049 		},
10050 
10051 		update : function() {
10052 			var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
10053 
10054 			tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth;
10055 			th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight;
10056 
10057 			if (!DOM.boxModel)
10058 				t.element.setStyles({width : tw + 2, height : th + 2});
10059 			else
10060 				t.element.setStyles({width : tw, height : th});
10061 
10062 			if (s.max_width)
10063 				DOM.setStyle(co, 'width', tw);
10064 
10065 			if (s.max_height) {
10066 				DOM.setStyle(co, 'height', th);
10067 
10068 				if (tb.clientHeight < s.max_height)
10069 					DOM.setStyle(co, 'overflow', 'hidden');
10070 			}
10071 		},
10072 
10073 		showMenu : function(x, y, px) {
10074 			var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
10075 
10076 			t.collapse(1);
10077 
10078 			if (t.isMenuVisible)
10079 				return;
10080 
10081 			if (!t.rendered) {
10082 				co = DOM.add(t.settings.container, t.renderNode());
10083 
10084 				each(t.items, function(o) {
10085 					o.postRender();
10086 				});
10087 
10088 				t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
10089 			} else
10090 				co = DOM.get('menu_' + t.id);
10091 
10092 			// Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
10093 			if (!tinymce.isOpera)
10094 				DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
10095 
10096 			DOM.show(co);
10097 			t.update();
10098 
10099 			x += s.offset_x || 0;
10100 			y += s.offset_y || 0;
10101 			vp.w -= 4;
10102 			vp.h -= 4;
10103 
10104 			// Move inside viewport if not submenu
10105 			if (s.constrain) {
10106 				w = co.clientWidth - ot;
10107 				h = co.clientHeight - ot;
10108 				mx = vp.x + vp.w;
10109 				my = vp.y + vp.h;
10110 
10111 				if ((x + s.vp_offset_x + w) > mx)
10112 					x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
10113 
10114 				if ((y + s.vp_offset_y + h) > my)
10115 					y = Math.max(0, (my - s.vp_offset_y) - h);
10116 			}
10117 
10118 			DOM.setStyles(co, {left : x , top : y});
10119 			t.element.update();
10120 
10121 			t.isMenuVisible = 1;
10122 			t.mouseClickFunc = Event.add(co, 'click', function(e) {
10123 				var m;
10124 
10125 				e = e.target;
10126 
10127 				if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
10128 					m = t.items[e.id];
10129 
10130 					if (m.isDisabled())
10131 						return;
10132 
10133 					dm = t;
10134 
10135 					while (dm) {
10136 						if (dm.hideMenu)
10137 							dm.hideMenu();
10138 
10139 						dm = dm.settings.parent;
10140 					}
10141 
10142 					if (m.settings.onclick)
10143 						m.settings.onclick(e);
10144 
10145 					return false; // Cancel to fix onbeforeunload problem
10146 				}
10147 			});
10148 
10149 			if (t.hasMenus()) {
10150 				t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
10151 					var m, r, mi;
10152 
10153 					e = e.target;
10154 					if (e && (e = DOM.getParent(e, 'tr'))) {
10155 						m = t.items[e.id];
10156 
10157 						if (t.lastMenu)
10158 							t.lastMenu.collapse(1);
10159 
10160 						if (m.isDisabled())
10161 							return;
10162 
10163 						if (e && DOM.hasClass(e, cp + 'ItemSub')) {
10164 							//p = DOM.getPos(s.container);
10165 							r = DOM.getRect(e);
10166 							m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
10167 							t.lastMenu = m;
10168 							DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
10169 						}
10170 					}
10171 				});
10172 			}
10173 			
10174 			Event.add(co, 'keydown', t._keyHandler, t);
10175 
10176 			t.onShowMenu.dispatch(t);
10177 
10178 			if (s.keyboard_focus) { 
10179 				t._setupKeyboardNav(); 
10180 			}
10181 		},
10182 
10183 		hideMenu : function(c) {
10184 			var t = this, co = DOM.get('menu_' + t.id), e;
10185 
10186 			if (!t.isMenuVisible)
10187 				return;
10188 
10189 			if (t.keyboardNav) t.keyboardNav.destroy();
10190 			Event.remove(co, 'mouseover', t.mouseOverFunc);
10191 			Event.remove(co, 'click', t.mouseClickFunc);
10192 			Event.remove(co, 'keydown', t._keyHandler);
10193 			DOM.hide(co);
10194 			t.isMenuVisible = 0;
10195 
10196 			if (!c)
10197 				t.collapse(1);
10198 
10199 			if (t.element)
10200 				t.element.hide();
10201 
10202 			if (e = DOM.get(t.id))
10203 				DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
10204 
10205 			t.onHideMenu.dispatch(t);
10206 		},
10207 
10208 		add : function(o) {
10209 			var t = this, co;
10210 
10211 			o = t.parent(o);
10212 
10213 			if (t.isRendered && (co = DOM.get('menu_' + t.id)))
10214 				t._add(DOM.select('tbody', co)[0], o);
10215 
10216 			return o;
10217 		},
10218 
10219 		collapse : function(d) {
10220 			this.parent(d);
10221 			this.hideMenu(1);
10222 		},
10223 
10224 		remove : function(o) {
10225 			DOM.remove(o.id);
10226 			this.destroy();
10227 
10228 			return this.parent(o);
10229 		},
10230 
10231 		destroy : function() {
10232 			var t = this, co = DOM.get('menu_' + t.id);
10233 
10234 			if (t.keyboardNav) t.keyboardNav.destroy();
10235 			Event.remove(co, 'mouseover', t.mouseOverFunc);
10236 			Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
10237 			Event.remove(co, 'click', t.mouseClickFunc);
10238 			Event.remove(co, 'keydown', t._keyHandler);
10239 
10240 			if (t.element)
10241 				t.element.remove();
10242 
10243 			DOM.remove(co);
10244 		},
10245 
10246 		renderNode : function() {
10247 			var t = this, s = t.settings, n, tb, co, w;
10248 
10249 			w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'});
10250 			if (t.settings.parent) {
10251 				DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
10252 			}
10253 			co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
10254 			t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
10255 
10256 			if (s.menu_line)
10257 				DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
10258 
10259 //			n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
10260 			n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
10261 			tb = DOM.add(n, 'tbody');
10262 
10263 			each(t.items, function(o) {
10264 				t._add(tb, o);
10265 			});
10266 
10267 			t.rendered = true;
10268 
10269 			return w;
10270 		},
10271 
10272 		// Internal functions
10273 		_setupKeyboardNav : function(){
10274 			var contextMenu, menuItems, t=this; 
10275 			contextMenu = DOM.get('menu_' + t.id);
10276 			menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
10277 			menuItems.splice(0,0,contextMenu);
10278 			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
10279 				root: 'menu_' + t.id,
10280 				items: menuItems,
10281 				onCancel: function() {
10282 					t.hideMenu();
10283 				},
10284 				enableUpDown: true
10285 			});
10286 			contextMenu.focus();
10287 		},
10288 
10289 		_keyHandler : function(evt) {
10290 			var t = this, e;
10291 			switch (evt.keyCode) {
10292 				case 37: // Left
10293 					if (t.settings.parent) {
10294 						t.hideMenu();
10295 						t.settings.parent.focus();
10296 						Event.cancel(evt);
10297 					}
10298 					break;
10299 				case 39: // Right
10300 					if (t.mouseOverFunc)
10301 						t.mouseOverFunc(evt);
10302 					break;
10303 			}
10304 		},
10305 
10306 		_add : function(tb, o) {
10307 			var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
10308 
10309 			if (s.separator) {
10310 				ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
10311 				DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
10312 
10313 				if (n = ro.previousSibling)
10314 					DOM.addClass(n, 'mceLast');
10315 
10316 				return;
10317 			}
10318 
10319 			n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
10320 			n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
10321 			n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
10322 
10323 			if (s.parent) {
10324 				DOM.setAttrib(a, 'aria-haspopup', 'true');
10325 				DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
10326 			}
10327 
10328 			DOM.addClass(it, s['class']);
10329 //			n = DOM.add(n, 'span', {'class' : 'item'});
10330 
10331 			ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
10332 
10333 			if (s.icon_src)
10334 				DOM.add(ic, 'img', {src : s.icon_src});
10335 
10336 			n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
10337 
10338 			if (o.settings.style) {
10339 				if (typeof o.settings.style == "function")
10340 					o.settings.style = o.settings.style();
10341 
10342 				DOM.setAttrib(n, 'style', o.settings.style);
10343 			}
10344 
10345 			if (tb.childNodes.length == 1)
10346 				DOM.addClass(ro, 'mceFirst');
10347 
10348 			if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
10349 				DOM.addClass(ro, 'mceFirst');
10350 
10351 			if (o.collapse)
10352 				DOM.addClass(ro, cp + 'ItemSub');
10353 
10354 			if (n = ro.previousSibling)
10355 				DOM.removeClass(n, 'mceLast');
10356 
10357 			DOM.addClass(ro, 'mceLast');
10358 		}
10359 	});
10360 })(tinymce);
10361 (function(tinymce) {
10362 	var DOM = tinymce.DOM;
10363 
10364 	tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
10365 		Button : function(id, s, ed) {
10366 			this.parent(id, s, ed);
10367 			this.classPrefix = 'mceButton';
10368 		},
10369 
10370 		renderHTML : function() {
10371 			var cp = this.classPrefix, s = this.settings, h, l;
10372 
10373 			l = DOM.encode(s.label || '');
10374 			h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
10375 			if (s.image && !(this.editor  &&this.editor.forcedHighContrastMode) )
10376 				h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
10377 			else
10378 				h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
10379 
10380 			h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 
10381 			h += '</a>';
10382 			return h;
10383 		},
10384 
10385 		postRender : function() {
10386 			var t = this, s = t.settings, imgBookmark;
10387 
10388 			// In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so
10389 			// need to keep the selection in case the selection is lost
10390 			if (tinymce.isIE && t.editor) {
10391 				tinymce.dom.Event.add(t.id, 'mousedown', function(e) {
10392 					var nodeName = t.editor.selection.getNode().nodeName;
10393 					imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null;
10394 				});
10395 			}
10396 			tinymce.dom.Event.add(t.id, 'click', function(e) {
10397 				if (!t.isDisabled()) {
10398 					// restore the selection in case the selection is lost in IE
10399 					if (tinymce.isIE && t.editor && imgBookmark !== null) {
10400 						t.editor.selection.moveToBookmark(imgBookmark);
10401 					}
10402 					return s.onclick.call(s.scope, e);
10403 				}
10404 			});
10405 			tinymce.dom.Event.add(t.id, 'keyup', function(e) {
10406 				if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR)
10407 					return s.onclick.call(s.scope, e);
10408 			});
10409 		}
10410 	});
10411 })(tinymce);
10412 
10413 (function(tinymce) {
10414 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
10415 
10416 	tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
10417 		ListBox : function(id, s, ed) {
10418 			var t = this;
10419 
10420 			t.parent(id, s, ed);
10421 
10422 			t.items = [];
10423 
10424 			t.onChange = new Dispatcher(t);
10425 
10426 			t.onPostRender = new Dispatcher(t);
10427 
10428 			t.onAdd = new Dispatcher(t);
10429 
10430 			t.onRenderMenu = new tinymce.util.Dispatcher(this);
10431 
10432 			t.classPrefix = 'mceListBox';
10433 			t.marked = {};
10434 		},
10435 
10436 		select : function(va) {
10437 			var t = this, fv, f;
10438 
10439 			t.marked = {};
10440 
10441 			if (va == undef)
10442 				return t.selectByIndex(-1);
10443 
10444 			// Is string or number make function selector
10445 			if (va && typeof(va)=="function")
10446 				f = va;
10447 			else {
10448 				f = function(v) {
10449 					return v == va;
10450 				};
10451 			}
10452 
10453 			// Do we need to do something?
10454 			if (va != t.selectedValue) {
10455 				// Find item
10456 				each(t.items, function(o, i) {
10457 					if (f(o.value)) {
10458 						fv = 1;
10459 						t.selectByIndex(i);
10460 						return false;
10461 					}
10462 				});
10463 
10464 				if (!fv)
10465 					t.selectByIndex(-1);
10466 			}
10467 		},
10468 
10469 		selectByIndex : function(idx) {
10470 			var t = this, e, o, label;
10471 
10472 			t.marked = {};
10473 
10474 			if (idx != t.selectedIndex) {
10475 				e = DOM.get(t.id + '_text');
10476 				label = DOM.get(t.id + '_voiceDesc');
10477 				o = t.items[idx];
10478 
10479 				if (o) {
10480 					t.selectedValue = o.value;
10481 					t.selectedIndex = idx;
10482 					DOM.setHTML(e, DOM.encode(o.title));
10483 					DOM.setHTML(label, t.settings.title + " - " + o.title);
10484 					DOM.removeClass(e, 'mceTitle');
10485 					DOM.setAttrib(t.id, 'aria-valuenow', o.title);
10486 				} else {
10487 					DOM.setHTML(e, DOM.encode(t.settings.title));
10488 					DOM.setHTML(label, DOM.encode(t.settings.title));
10489 					DOM.addClass(e, 'mceTitle');
10490 					t.selectedValue = t.selectedIndex = null;
10491 					DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
10492 				}
10493 				e = 0;
10494 			}
10495 		},
10496 
10497 		mark : function(value) {
10498 			this.marked[value] = true;
10499 		},
10500 
10501 		add : function(n, v, o) {
10502 			var t = this;
10503 
10504 			o = o || {};
10505 			o = tinymce.extend(o, {
10506 				title : n,
10507 				value : v
10508 			});
10509 
10510 			t.items.push(o);
10511 			t.onAdd.dispatch(t, o);
10512 		},
10513 
10514 		getLength : function() {
10515 			return this.items.length;
10516 		},
10517 
10518 		renderHTML : function() {
10519 			var h = '', t = this, s = t.settings, cp = t.classPrefix;
10520 
10521 			h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
10522 			h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 
10523 			h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
10524 			h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
10525 			h += '</tr></tbody></table></span>';
10526 
10527 			return h;
10528 		},
10529 
10530 		showMenu : function() {
10531 			var t = this, p2, e = DOM.get(this.id), m;
10532 
10533 			if (t.isDisabled() || t.items.length === 0)
10534 				return;
10535 
10536 			if (t.menu && t.menu.isMenuVisible)
10537 				return t.hideMenu();
10538 
10539 			if (!t.isMenuRendered) {
10540 				t.renderMenu();
10541 				t.isMenuRendered = true;
10542 			}
10543 
10544 			p2 = DOM.getPos(e);
10545 
10546 			m = t.menu;
10547 			m.settings.offset_x = p2.x;
10548 			m.settings.offset_y = p2.y;
10549 			m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
10550 
10551 			// Select in menu
10552 			each(t.items, function(o) {
10553 				if (m.items[o.id]) {
10554 					m.items[o.id].setSelected(0);
10555 				}
10556 			});
10557 
10558 			each(t.items, function(o) {
10559 				if (m.items[o.id] && t.marked[o.value]) {
10560 					m.items[o.id].setSelected(1);
10561 				}
10562 
10563 				if (o.value === t.selectedValue) {
10564 					m.items[o.id].setSelected(1);
10565 				}
10566 			});
10567 
10568 			m.showMenu(0, e.clientHeight);
10569 
10570 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
10571 			DOM.addClass(t.id, t.classPrefix + 'Selected');
10572 
10573 			//DOM.get(t.id + '_text').focus();
10574 		},
10575 
10576 		hideMenu : function(e) {
10577 			var t = this;
10578 
10579 			if (t.menu && t.menu.isMenuVisible) {
10580 				DOM.removeClass(t.id, t.classPrefix + 'Selected');
10581 
10582 				// Prevent double toogles by canceling the mouse click event to the button
10583 				if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
10584 					return;
10585 
10586 				if (!e || !DOM.getParent(e.target, '.mceMenu')) {
10587 					DOM.removeClass(t.id, t.classPrefix + 'Selected');
10588 					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
10589 					t.menu.hideMenu();
10590 				}
10591 			}
10592 		},
10593 
10594 		renderMenu : function() {
10595 			var t = this, m;
10596 
10597 			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
10598 				menu_line : 1,
10599 				'class' : t.classPrefix + 'Menu mceNoIcons',
10600 				max_width : 250,
10601 				max_height : 150
10602 			});
10603 
10604 			m.onHideMenu.add(function() {
10605 				t.hideMenu();
10606 				t.focus();
10607 			});
10608 
10609 			m.add({
10610 				title : t.settings.title,
10611 				'class' : 'mceMenuItemTitle',
10612 				onclick : function() {
10613 					if (t.settings.onselect('') !== false)
10614 						t.select(''); // Must be runned after
10615 				}
10616 			});
10617 
10618 			each(t.items, function(o) {
10619 				// No value then treat it as a title
10620 				if (o.value === undef) {
10621 					m.add({
10622 						title : o.title,
10623 						role : "option",
10624 						'class' : 'mceMenuItemTitle',
10625 						onclick : function() {
10626 							if (t.settings.onselect('') !== false)
10627 								t.select(''); // Must be runned after
10628 						}
10629 					});
10630 				} else {
10631 					o.id = DOM.uniqueId();
10632 					o.role= "option";
10633 					o.onclick = function() {
10634 						if (t.settings.onselect(o.value) !== false)
10635 							t.select(o.value); // Must be runned after
10636 					};
10637 
10638 					m.add(o);
10639 				}
10640 			});
10641 
10642 			t.onRenderMenu.dispatch(t, m);
10643 			t.menu = m;
10644 		},
10645 
10646 		postRender : function() {
10647 			var t = this, cp = t.classPrefix;
10648 
10649 			Event.add(t.id, 'click', t.showMenu, t);
10650 			Event.add(t.id, 'keydown', function(evt) {
10651 				if (evt.keyCode == 32) { // Space
10652 					t.showMenu(evt);
10653 					Event.cancel(evt);
10654 				}
10655 			});
10656 			Event.add(t.id, 'focus', function() {
10657 				if (!t._focused) {
10658 					t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
10659 						if (e.keyCode == 40) {
10660 							t.showMenu();
10661 							Event.cancel(e);
10662 						}
10663 					});
10664 					t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
10665 						var v;
10666 						if (e.keyCode == 13) {
10667 							// Fake select on enter
10668 							v = t.selectedValue;
10669 							t.selectedValue = null; // Needs to be null to fake change
10670 							Event.cancel(e);
10671 							t.settings.onselect(v);
10672 						}
10673 					});
10674 				}
10675 
10676 				t._focused = 1;
10677 			});
10678 			Event.add(t.id, 'blur', function() {
10679 				Event.remove(t.id, 'keydown', t.keyDownHandler);
10680 				Event.remove(t.id, 'keypress', t.keyPressHandler);
10681 				t._focused = 0;
10682 			});
10683 
10684 			// Old IE doesn't have hover on all elements
10685 			if (tinymce.isIE6 || !DOM.boxModel) {
10686 				Event.add(t.id, 'mouseover', function() {
10687 					if (!DOM.hasClass(t.id, cp + 'Disabled'))
10688 						DOM.addClass(t.id, cp + 'Hover');
10689 				});
10690 
10691 				Event.add(t.id, 'mouseout', function() {
10692 					if (!DOM.hasClass(t.id, cp + 'Disabled'))
10693 						DOM.removeClass(t.id, cp + 'Hover');
10694 				});
10695 			}
10696 
10697 			t.onPostRender.dispatch(t, DOM.get(t.id));
10698 		},
10699 
10700 		destroy : function() {
10701 			this.parent();
10702 
10703 			Event.clear(this.id + '_text');
10704 			Event.clear(this.id + '_open');
10705 		}
10706 	});
10707 })(tinymce);
10708 
10709 (function(tinymce) {
10710 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
10711 
10712 	tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
10713 		NativeListBox : function(id, s) {
10714 			this.parent(id, s);
10715 			this.classPrefix = 'mceNativeListBox';
10716 		},
10717 
10718 		setDisabled : function(s) {
10719 			DOM.get(this.id).disabled = s;
10720 			this.setAriaProperty('disabled', s);
10721 		},
10722 
10723 		isDisabled : function() {
10724 			return DOM.get(this.id).disabled;
10725 		},
10726 
10727 		select : function(va) {
10728 			var t = this, fv, f;
10729 
10730 			if (va == undef)
10731 				return t.selectByIndex(-1);
10732 
10733 			// Is string or number make function selector
10734 			if (va && typeof(va)=="function")
10735 				f = va;
10736 			else {
10737 				f = function(v) {
10738 					return v == va;
10739 				};
10740 			}
10741 
10742 			// Do we need to do something?
10743 			if (va != t.selectedValue) {
10744 				// Find item
10745 				each(t.items, function(o, i) {
10746 					if (f(o.value)) {
10747 						fv = 1;
10748 						t.selectByIndex(i);
10749 						return false;
10750 					}
10751 				});
10752 
10753 				if (!fv)
10754 					t.selectByIndex(-1);
10755 			}
10756 		},
10757 
10758 		selectByIndex : function(idx) {
10759 			DOM.get(this.id).selectedIndex = idx + 1;
10760 			this.selectedValue = this.items[idx] ? this.items[idx].value : null;
10761 		},
10762 
10763 		add : function(n, v, a) {
10764 			var o, t = this;
10765 
10766 			a = a || {};
10767 			a.value = v;
10768 
10769 			if (t.isRendered())
10770 				DOM.add(DOM.get(this.id), 'option', a, n);
10771 
10772 			o = {
10773 				title : n,
10774 				value : v,
10775 				attribs : a
10776 			};
10777 
10778 			t.items.push(o);
10779 			t.onAdd.dispatch(t, o);
10780 		},
10781 
10782 		getLength : function() {
10783 			return this.items.length;
10784 		},
10785 
10786 		renderHTML : function() {
10787 			var h, t = this;
10788 
10789 			h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
10790 
10791 			each(t.items, function(it) {
10792 				h += DOM.createHTML('option', {value : it.value}, it.title);
10793 			});
10794 
10795 			h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
10796 			h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
10797 			return h;
10798 		},
10799 
10800 		postRender : function() {
10801 			var t = this, ch, changeListenerAdded = true;
10802 
10803 			t.rendered = true;
10804 
10805 			function onChange(e) {
10806 				var v = t.items[e.target.selectedIndex - 1];
10807 
10808 				if (v && (v = v.value)) {
10809 					t.onChange.dispatch(t, v);
10810 
10811 					if (t.settings.onselect)
10812 						t.settings.onselect(v);
10813 				}
10814 			};
10815 
10816 			Event.add(t.id, 'change', onChange);
10817 
10818 			// Accessibility keyhandler
10819 			Event.add(t.id, 'keydown', function(e) {
10820 				var bf;
10821 
10822 				Event.remove(t.id, 'change', ch);
10823 				changeListenerAdded = false;
10824 
10825 				bf = Event.add(t.id, 'blur', function() {
10826 					if (changeListenerAdded) return;
10827 					changeListenerAdded = true;
10828 					Event.add(t.id, 'change', onChange);
10829 					Event.remove(t.id, 'blur', bf);
10830 				});
10831 
10832 				//prevent default left and right keys on chrome - so that the keyboard navigation is used.
10833 				if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) {
10834 					return Event.prevent(e);
10835 				}
10836 				
10837 				if (e.keyCode == 13 || e.keyCode == 32) {
10838 					onChange(e);
10839 					return Event.cancel(e);
10840 				}
10841 			});
10842 
10843 			t.onPostRender.dispatch(t, DOM.get(t.id));
10844 		}
10845 	});
10846 })(tinymce);
10847 
10848 (function(tinymce) {
10849 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
10850 
10851 	tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
10852 		MenuButton : function(id, s, ed) {
10853 			this.parent(id, s, ed);
10854 
10855 			this.onRenderMenu = new tinymce.util.Dispatcher(this);
10856 
10857 			s.menu_container = s.menu_container || DOM.doc.body;
10858 		},
10859 
10860 		showMenu : function() {
10861 			var t = this, p1, p2, e = DOM.get(t.id), m;
10862 
10863 			if (t.isDisabled())
10864 				return;
10865 
10866 			if (!t.isMenuRendered) {
10867 				t.renderMenu();
10868 				t.isMenuRendered = true;
10869 			}
10870 
10871 			if (t.isMenuVisible)
10872 				return t.hideMenu();
10873 
10874 			p1 = DOM.getPos(t.settings.menu_container);
10875 			p2 = DOM.getPos(e);
10876 
10877 			m = t.menu;
10878 			m.settings.offset_x = p2.x;
10879 			m.settings.offset_y = p2.y;
10880 			m.settings.vp_offset_x = p2.x;
10881 			m.settings.vp_offset_y = p2.y;
10882 			m.settings.keyboard_focus = t._focused;
10883 			m.showMenu(0, e.firstChild.clientHeight);
10884 
10885 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
10886 			t.setState('Selected', 1);
10887 
10888 			t.isMenuVisible = 1;
10889 		},
10890 
10891 		renderMenu : function() {
10892 			var t = this, m;
10893 
10894 			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
10895 				menu_line : 1,
10896 				'class' : this.classPrefix + 'Menu',
10897 				icons : t.settings.icons
10898 			});
10899 
10900 			m.onHideMenu.add(function() {
10901 				t.hideMenu();
10902 				t.focus();
10903 			});
10904 
10905 			t.onRenderMenu.dispatch(t, m);
10906 			t.menu = m;
10907 		},
10908 
10909 		hideMenu : function(e) {
10910 			var t = this;
10911 
10912 			// Prevent double toogles by canceling the mouse click event to the button
10913 			if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
10914 				return;
10915 
10916 			if (!e || !DOM.getParent(e.target, '.mceMenu')) {
10917 				t.setState('Selected', 0);
10918 				Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
10919 				if (t.menu)
10920 					t.menu.hideMenu();
10921 			}
10922 
10923 			t.isMenuVisible = 0;
10924 		},
10925 
10926 		postRender : function() {
10927 			var t = this, s = t.settings;
10928 
10929 			Event.add(t.id, 'click', function() {
10930 				if (!t.isDisabled()) {
10931 					if (s.onclick)
10932 						s.onclick(t.value);
10933 
10934 					t.showMenu();
10935 				}
10936 			});
10937 		}
10938 	});
10939 })(tinymce);
10940 
10941 (function(tinymce) {
10942 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
10943 
10944 	tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
10945 		SplitButton : function(id, s, ed) {
10946 			this.parent(id, s, ed);
10947 			this.classPrefix = 'mceSplitButton';
10948 		},
10949 
10950 		renderHTML : function() {
10951 			var h, t = this, s = t.settings, h1;
10952 
10953 			h = '<tbody><tr>';
10954 
10955 			if (s.image)
10956 				h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
10957 			else
10958 				h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
10959 
10960 			h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
10961 			h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
10962 	
10963 			h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
10964 			h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
10965 
10966 			h += '</tr></tbody>';
10967 			h = DOM.createHTML('table', { role: 'presentation',   'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
10968 			return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
10969 		},
10970 
10971 		postRender : function() {
10972 			var t = this, s = t.settings, activate;
10973 
10974 			if (s.onclick) {
10975 				activate = function(evt) {
10976 					if (!t.isDisabled()) {
10977 						s.onclick(t.value);
10978 						Event.cancel(evt);
10979 					}
10980 				};
10981 				Event.add(t.id + '_action', 'click', activate);
10982 				Event.add(t.id, ['click', 'keydown'], function(evt) {
10983 					var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
10984 					if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
10985 						activate();
10986 						Event.cancel(evt);
10987 					} else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
10988 						t.showMenu();
10989 						Event.cancel(evt);
10990 					}
10991 				});
10992 			}
10993 
10994 			Event.add(t.id + '_open', 'click', function (evt) {
10995 				t.showMenu();
10996 				Event.cancel(evt);
10997 			});
10998 			Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
10999 			Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
11000 
11001 			// Old IE doesn't have hover on all elements
11002 			if (tinymce.isIE6 || !DOM.boxModel) {
11003 				Event.add(t.id, 'mouseover', function() {
11004 					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
11005 						DOM.addClass(t.id, 'mceSplitButtonHover');
11006 				});
11007 
11008 				Event.add(t.id, 'mouseout', function() {
11009 					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
11010 						DOM.removeClass(t.id, 'mceSplitButtonHover');
11011 				});
11012 			}
11013 		},
11014 
11015 		destroy : function() {
11016 			this.parent();
11017 
11018 			Event.clear(this.id + '_action');
11019 			Event.clear(this.id + '_open');
11020 			Event.clear(this.id);
11021 		}
11022 	});
11023 })(tinymce);
11024 
11025 (function(tinymce) {
11026 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
11027 
11028 	tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
11029 		ColorSplitButton : function(id, s, ed) {
11030 			var t = this;
11031 
11032 			t.parent(id, s, ed);
11033 
11034 			t.settings = s = tinymce.extend({
11035 				colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
11036 				grid_width : 8,
11037 				default_color : '#888888'
11038 			}, t.settings);
11039 
11040 			t.onShowMenu = new tinymce.util.Dispatcher(t);
11041 
11042 			t.onHideMenu = new tinymce.util.Dispatcher(t);
11043 
11044 			t.value = s.default_color;
11045 		},
11046 
11047 		showMenu : function() {
11048 			var t = this, r, p, e, p2;
11049 
11050 			if (t.isDisabled())
11051 				return;
11052 
11053 			if (!t.isMenuRendered) {
11054 				t.renderMenu();
11055 				t.isMenuRendered = true;
11056 			}
11057 
11058 			if (t.isMenuVisible)
11059 				return t.hideMenu();
11060 
11061 			e = DOM.get(t.id);
11062 			DOM.show(t.id + '_menu');
11063 			DOM.addClass(e, 'mceSplitButtonSelected');
11064 			p2 = DOM.getPos(e);
11065 			DOM.setStyles(t.id + '_menu', {
11066 				left : p2.x,
11067 				top : p2.y + e.firstChild.clientHeight,
11068 				zIndex : 200000
11069 			});
11070 			e = 0;
11071 
11072 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
11073 			t.onShowMenu.dispatch(t);
11074 
11075 			if (t._focused) {
11076 				t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
11077 					if (e.keyCode == 27)
11078 						t.hideMenu();
11079 				});
11080 
11081 				DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
11082 			}
11083 
11084 			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
11085 				root: t.id + '_menu',
11086 				items: DOM.select('a', t.id + '_menu'),
11087 				onCancel: function() {
11088 					t.hideMenu();
11089 					t.focus();
11090 				}
11091 			});
11092 
11093 			t.keyboardNav.focus();
11094 			t.isMenuVisible = 1;
11095 		},
11096 
11097 		hideMenu : function(e) {
11098 			var t = this;
11099 
11100 			if (t.isMenuVisible) {
11101 				// Prevent double toogles by canceling the mouse click event to the button
11102 				if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
11103 					return;
11104 
11105 				if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
11106 					DOM.removeClass(t.id, 'mceSplitButtonSelected');
11107 					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
11108 					Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
11109 					DOM.hide(t.id + '_menu');
11110 				}
11111 
11112 				t.isMenuVisible = 0;
11113 				t.onHideMenu.dispatch();
11114 				t.keyboardNav.destroy();
11115 			}
11116 		},
11117 
11118 		renderMenu : function() {
11119 			var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
11120 
11121 			w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s.menu_class + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
11122 			m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
11123 			DOM.add(m, 'span', {'class' : 'mceMenuLine'});
11124 
11125 			n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
11126 			tb = DOM.add(n, 'tbody');
11127 
11128 			// Generate color grid
11129 			i = 0;
11130 			each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
11131 				c = c.replace(/^#/, '');
11132 
11133 				if (!i--) {
11134 					tr = DOM.add(tb, 'tr');
11135 					i = s.grid_width - 1;
11136 				}
11137 
11138 				n = DOM.add(tr, 'td');
11139 				var settings = {
11140 					href : 'javascript:;',
11141 					style : {
11142 						backgroundColor : '#' + c
11143 					},
11144 					'title': t.editor.getLang('colors.' + c, c),
11145 					'data-mce-color' : '#' + c
11146 				};
11147 
11148 				// adding a proper ARIA role = button causes JAWS to read things incorrectly on IE.
11149 				if (!tinymce.isIE ) {
11150 					settings.role = 'option';
11151 				}
11152 
11153 				n = DOM.add(n, 'a', settings);
11154 
11155 				if (t.editor.forcedHighContrastMode) {
11156 					n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
11157 					if (n.getContext && (context = n.getContext("2d"))) {
11158 						context.fillStyle = '#' + c;
11159 						context.fillRect(0, 0, 16, 16);
11160 					} else {
11161 						// No point leaving a canvas element around if it's not supported for drawing on anyway.
11162 						DOM.remove(n);
11163 					}
11164 				}
11165 			});
11166 
11167 			if (s.more_colors_func) {
11168 				n = DOM.add(tb, 'tr');
11169 				n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
11170 				n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
11171 
11172 				Event.add(n, 'click', function(e) {
11173 					s.more_colors_func.call(s.more_colors_scope || this);
11174 					return Event.cancel(e); // Cancel to fix onbeforeunload problem
11175 				});
11176 			}
11177 
11178 			DOM.addClass(m, 'mceColorSplitMenu');
11179 
11180 			// Prevent IE from scrolling and hindering click to occur #4019
11181 			Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
11182 
11183 			Event.add(t.id + '_menu', 'click', function(e) {
11184 				var c;
11185 
11186 				e = DOM.getParent(e.target, 'a', tb);
11187 
11188 				if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
11189 					t.setColor(c);
11190 
11191 				return false; // Prevent IE auto save warning
11192 			});
11193 
11194 			return w;
11195 		},
11196 
11197 		setColor : function(c) {
11198 			this.displayColor(c);
11199 			this.hideMenu();
11200 			this.settings.onselect(c);
11201 		},
11202 		
11203 		displayColor : function(c) {
11204 			var t = this;
11205 
11206 			DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
11207 
11208 			t.value = c;
11209 		},
11210 
11211 		postRender : function() {
11212 			var t = this, id = t.id;
11213 
11214 			t.parent();
11215 			DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
11216 			DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
11217 		},
11218 
11219 		destroy : function() {
11220 			var self = this;
11221 
11222 			self.parent();
11223 
11224 			Event.clear(self.id + '_menu');
11225 			Event.clear(self.id + '_more');
11226 			DOM.remove(self.id + '_menu');
11227 
11228 			if (self.keyboardNav) {
11229 				self.keyboardNav.destroy();
11230 			}
11231 		}
11232 	});
11233 })(tinymce);
11234 
11235 (function(tinymce) {
11236 // Shorten class names
11237 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
11238 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
11239 	renderHTML : function() {
11240 		var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
11241 
11242 		h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
11243 		//TODO: ACC test this out - adding a role = application for getting the landmarks working well.
11244 		h.push("<span role='application'>");
11245 		h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
11246 		each(controls, function(toolbar) {
11247 			h.push(toolbar.renderHTML());
11248 		});
11249 		h.push("</span>");
11250 		h.push('</div>');
11251 
11252 		return h.join('');
11253 	},
11254 	
11255 	focus : function() {
11256 		var t = this;
11257 		dom.get(t.id).focus();
11258 	},
11259 	
11260 	postRender : function() {
11261 		var t = this, items = [];
11262 
11263 		each(t.controls, function(toolbar) {
11264 			each (toolbar.controls, function(control) {
11265 				if (control.id) {
11266 					items.push(control);
11267 				}
11268 			});
11269 		});
11270 
11271 		t.keyNav = new tinymce.ui.KeyboardNavigation({
11272 			root: t.id,
11273 			items: items,
11274 			onCancel: function() {
11275 				//Move focus if webkit so that navigation back will read the item.
11276 				if (tinymce.isWebKit) {
11277 					dom.get(t.editor.id+"_ifr").focus();
11278 				}
11279 				t.editor.focus();
11280 			},
11281 			excludeFromTabOrder: !t.settings.tab_focus_toolbar
11282 		});
11283 	},
11284 	
11285 	destroy : function() {
11286 		var self = this;
11287 
11288 		self.parent();
11289 		self.keyNav.destroy();
11290 		Event.clear(self.id);
11291 	}
11292 });
11293 })(tinymce);
11294 
11295 (function(tinymce) {
11296 // Shorten class names
11297 var dom = tinymce.DOM, each = tinymce.each;
11298 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
11299 	renderHTML : function() {
11300 		var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
11301 
11302 		cl = t.controls;
11303 		for (i=0; i<cl.length; i++) {
11304 			// Get current control, prev control, next control and if the control is a list box or not
11305 			co = cl[i];
11306 			pr = cl[i - 1];
11307 			nx = cl[i + 1];
11308 
11309 			// Add toolbar start
11310 			if (i === 0) {
11311 				c = 'mceToolbarStart';
11312 
11313 				if (co.Button)
11314 					c += ' mceToolbarStartButton';
11315 				else if (co.SplitButton)
11316 					c += ' mceToolbarStartSplitButton';
11317 				else if (co.ListBox)
11318 					c += ' mceToolbarStartListBox';
11319 
11320 				h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
11321 			}
11322 
11323 			// Add toolbar end before list box and after the previous button
11324 			// This is to fix the o2k7 editor skins
11325 			if (pr && co.ListBox) {
11326 				if (pr.Button || pr.SplitButton)
11327 					h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
11328 			}
11329 
11330 			// Render control HTML
11331 
11332 			// IE 8 quick fix, needed to propertly generate a hit area for anchors
11333 			if (dom.stdMode)
11334 				h += '<td style="position: relative">' + co.renderHTML() + '</td>';
11335 			else
11336 				h += '<td>' + co.renderHTML() + '</td>';
11337 
11338 			// Add toolbar start after list box and before the next button
11339 			// This is to fix the o2k7 editor skins
11340 			if (nx && co.ListBox) {
11341 				if (nx.Button || nx.SplitButton)
11342 					h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
11343 			}
11344 		}
11345 
11346 		c = 'mceToolbarEnd';
11347 
11348 		if (co.Button)
11349 			c += ' mceToolbarEndButton';
11350 		else if (co.SplitButton)
11351 			c += ' mceToolbarEndSplitButton';
11352 		else if (co.ListBox)
11353 			c += ' mceToolbarEndListBox';
11354 
11355 		h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
11356 
11357 		return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>');
11358 	}
11359 });
11360 })(tinymce);
11361 
11362 (function(tinymce) {
11363 	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
11364 
11365 	tinymce.create('tinymce.AddOnManager', {
11366 		AddOnManager : function() {
11367 			var self = this;
11368 
11369 			self.items = [];
11370 			self.urls = {};
11371 			self.lookup = {};
11372 			self.onAdd = new Dispatcher(self);
11373 		},
11374 
11375 		get : function(n) {
11376 			if (this.lookup[n]) {
11377 				return this.lookup[n].instance;
11378 			} else {
11379 				return undefined;
11380 			}
11381 		},
11382 
11383 		dependencies : function(n) {
11384 			var result;
11385 			if (this.lookup[n]) {
11386 				result = this.lookup[n].dependencies;
11387 			}
11388 			return result || [];
11389 		},
11390 
11391 		requireLangPack : function(n) {
11392 			var s = tinymce.settings;
11393 
11394 			if (s && s.language && s.language_load !== false)
11395 				tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
11396 		},
11397 
11398 		add : function(id, o, dependencies) {
11399 			this.items.push(o);
11400 			this.lookup[id] = {instance:o, dependencies:dependencies};
11401 			this.onAdd.dispatch(this, id, o);
11402 
11403 			return o;
11404 		},
11405 		createUrl: function(baseUrl, dep) {
11406 			if (typeof dep === "object") {
11407 				return dep
11408 			} else {
11409 				return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
11410 			}
11411 		},
11412 
11413 		addComponents: function(pluginName, scripts) {
11414 			var pluginUrl = this.urls[pluginName];
11415 			tinymce.each(scripts, function(script){
11416 				tinymce.ScriptLoader.add(pluginUrl+"/"+script);	
11417 			});
11418 		},
11419 
11420 		load : function(n, u, cb, s) {
11421 			var t = this, url = u;
11422 
11423 			function loadDependencies() {
11424 				var dependencies = t.dependencies(n);
11425 				tinymce.each(dependencies, function(dep) {
11426 					var newUrl = t.createUrl(u, dep);
11427 					t.load(newUrl.resource, newUrl, undefined, undefined);
11428 				});
11429 				if (cb) {
11430 					if (s) {
11431 						cb.call(s);
11432 					} else {
11433 						cb.call(tinymce.ScriptLoader);
11434 					}
11435 				}
11436 			}
11437 
11438 			if (t.urls[n])
11439 				return;
11440 			if (typeof u === "object")
11441 				url = u.prefix + u.resource + u.suffix;
11442 
11443 			if (url.indexOf('/') !== 0 && url.indexOf('://') == -1)
11444 				url = tinymce.baseURL + '/' + url;
11445 
11446 			t.urls[n] = url.substring(0, url.lastIndexOf('/'));
11447 
11448 			if (t.lookup[n]) {
11449 				loadDependencies();
11450 			} else {
11451 				tinymce.ScriptLoader.add(url, loadDependencies, s);
11452 			}
11453 		}
11454 	});
11455 
11456 	// Create plugin and theme managers
11457 	tinymce.PluginManager = new tinymce.AddOnManager();
11458 	tinymce.ThemeManager = new tinymce.AddOnManager();
11459 }(tinymce));
11460 
11461 (function(tinymce) {
11462 	// Shorten names
11463 	var each = tinymce.each, extend = tinymce.extend,
11464 		DOM = tinymce.DOM, Event = tinymce.dom.Event,
11465 		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
11466 		explode = tinymce.explode,
11467 		Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0;
11468 
11469 	// Setup some URLs where the editor API is located and where the document is
11470 	tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
11471 	if (!/[\/\\]$/.test(tinymce.documentBaseURL))
11472 		tinymce.documentBaseURL += '/';
11473 
11474 	tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
11475 
11476 	tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
11477 
11478 	// Add before unload listener
11479 	// This was required since IE was leaking memory if you added and removed beforeunload listeners
11480 	// with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
11481 	tinymce.onBeforeUnload = new Dispatcher(tinymce);
11482 
11483 	// Must be on window or IE will leak if the editor is placed in frame or iframe
11484 	Event.add(window, 'beforeunload', function(e) {
11485 		tinymce.onBeforeUnload.dispatch(tinymce, e);
11486 	});
11487 
11488 	tinymce.onAddEditor = new Dispatcher(tinymce);
11489 
11490 	tinymce.onRemoveEditor = new Dispatcher(tinymce);
11491 
11492 	tinymce.EditorManager = extend(tinymce, {
11493 		editors : [],
11494 
11495 		i18n : {},
11496 
11497 		activeEditor : null,
11498 
11499 		init : function(s) {
11500 			var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
11501 
11502 			function createId(elm) {
11503 				var id = elm.id;
11504 	
11505 				// Use element id, or unique name or generate a unique id
11506 				if (!id) {
11507 					id = elm.name;
11508 	
11509 					if (id && !DOM.get(id)) {
11510 						id = elm.name;
11511 					} else {
11512 						// Generate unique name
11513 						id = DOM.uniqueId();
11514 					}
11515 
11516 					elm.setAttribute('id', id);
11517 				}
11518 
11519 				return id;
11520 			};
11521 
11522 			function execCallback(se, n, s) {
11523 				var f = se[n];
11524 
11525 				if (!f)
11526 					return;
11527 
11528 				if (tinymce.is(f, 'string')) {
11529 					s = f.replace(/\.\w+$/, '');
11530 					s = s ? tinymce.resolve(s) : 0;
11531 					f = tinymce.resolve(f);
11532 				}
11533 
11534 				return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
11535 			};
11536 
11537 			function hasClass(n, c) {
11538 				return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
11539 			};
11540 
11541 			t.settings = s;
11542 
11543 			// Legacy call
11544 			Event.bind(window, 'ready', function() {
11545 				var l, co;
11546 
11547 				execCallback(s, 'onpageload');
11548 
11549 				switch (s.mode) {
11550 					case "exact":
11551 						l = s.elements || '';
11552 
11553 						if(l.length > 0) {
11554 							each(explode(l), function(v) {
11555 								if (DOM.get(v)) {
11556 									ed = new tinymce.Editor(v, s);
11557 									el.push(ed);
11558 									ed.render(1);
11559 								} else {
11560 									each(document.forms, function(f) {
11561 										each(f.elements, function(e) {
11562 											if (e.name === v) {
11563 												v = 'mce_editor_' + instanceCounter++;
11564 												DOM.setAttrib(e, 'id', v);
11565 
11566 												ed = new tinymce.Editor(v, s);
11567 												el.push(ed);
11568 												ed.render(1);
11569 											}
11570 										});
11571 									});
11572 								}
11573 							});
11574 						}
11575 						break;
11576 
11577 					case "textareas":
11578 					case "specific_textareas":
11579 						each(DOM.select('textarea'), function(elm) {
11580 							if (s.editor_deselector && hasClass(elm, s.editor_deselector))
11581 								return;
11582 
11583 							if (!s.editor_selector || hasClass(elm, s.editor_selector)) {
11584 								ed = new tinymce.Editor(createId(elm), s);
11585 								el.push(ed);
11586 								ed.render(1);
11587 							}
11588 						});
11589 						break;
11590 					
11591 					default:
11592 						if (s.types) {
11593 							// Process type specific selector
11594 							each(s.types, function(type) {
11595 								each(DOM.select(type.selector), function(elm) {
11596 									var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type));
11597 									el.push(editor);
11598 									editor.render(1);
11599 								});
11600 							});
11601 						} else if (s.selector) {
11602 							// Process global selector
11603 							each(DOM.select(s.selector), function(elm) {
11604 								var editor = new tinymce.Editor(createId(elm), s);
11605 								el.push(editor);
11606 								editor.render(1);
11607 							});
11608 						}
11609 				}
11610 
11611 				// Call onInit when all editors are initialized
11612 				if (s.oninit) {
11613 					l = co = 0;
11614 
11615 					each(el, function(ed) {
11616 						co++;
11617 
11618 						if (!ed.initialized) {
11619 							// Wait for it
11620 							ed.onInit.add(function() {
11621 								l++;
11622 
11623 								// All done
11624 								if (l == co)
11625 									execCallback(s, 'oninit');
11626 							});
11627 						} else
11628 							l++;
11629 
11630 						// All done
11631 						if (l == co)
11632 							execCallback(s, 'oninit');					
11633 					});
11634 				}
11635 			});
11636 		},
11637 
11638 		get : function(id) {
11639 			if (id === undef)
11640 				return this.editors;
11641 
11642 			return this.editors[id];
11643 		},
11644 
11645 		getInstanceById : function(id) {
11646 			return this.get(id);
11647 		},
11648 
11649 		add : function(editor) {
11650 			var self = this, editors = self.editors;
11651 
11652 			// Add named and index editor instance
11653 			editors[editor.id] = editor;
11654 			editors.push(editor);
11655 
11656 			self._setActive(editor);
11657 			self.onAddEditor.dispatch(self, editor);
11658 
11659 
11660 			// Patch the tinymce.Editor instance with jQuery adapter logic
11661 			if (tinymce.adapter)
11662 				tinymce.adapter.patchEditor(editor);
11663 
11664 
11665 			return editor;
11666 		},
11667 
11668 		remove : function(editor) {
11669 			var t = this, i, editors = t.editors;
11670 
11671 			// Not in the collection
11672 			if (!editors[editor.id])
11673 				return null;
11674 
11675 			delete editors[editor.id];
11676 
11677 			for (i = 0; i < editors.length; i++) {
11678 				if (editors[i] == editor) {
11679 					editors.splice(i, 1);
11680 					break;
11681 				}
11682 			}
11683 
11684 			// Select another editor since the active one was removed
11685 			if (t.activeEditor == editor)
11686 				t._setActive(editors[0]);
11687 
11688 			editor.destroy();
11689 			t.onRemoveEditor.dispatch(t, editor);
11690 
11691 			return editor;
11692 		},
11693 
11694 		execCommand : function(c, u, v) {
11695 			var t = this, ed = t.get(v), w;
11696 
11697 			function clr() {
11698 				ed.destroy();
11699 				w.detachEvent('onunload', clr);
11700 				w = w.tinyMCE = w.tinymce = null; // IE leak
11701 			};
11702 
11703 			// Manager commands
11704 			switch (c) {
11705 				case "mceFocus":
11706 					ed.focus();
11707 					return true;
11708 
11709 				case "mceAddEditor":
11710 				case "mceAddControl":
11711 					if (!t.get(v))
11712 						new tinymce.Editor(v, t.settings).render();
11713 
11714 					return true;
11715 
11716 				case "mceAddFrameControl":
11717 					w = v.window;
11718 
11719 					// Add tinyMCE global instance and tinymce namespace to specified window
11720 					w.tinyMCE = tinyMCE;
11721 					w.tinymce = tinymce;
11722 
11723 					tinymce.DOM.doc = w.document;
11724 					tinymce.DOM.win = w;
11725 
11726 					ed = new tinymce.Editor(v.element_id, v);
11727 					ed.render();
11728 
11729 					// Fix IE memory leaks
11730 					if (tinymce.isIE) {
11731 						w.attachEvent('onunload', clr);
11732 					}
11733 
11734 					v.page_window = null;
11735 
11736 					return true;
11737 
11738 				case "mceRemoveEditor":
11739 				case "mceRemoveControl":
11740 					if (ed)
11741 						ed.remove();
11742 
11743 					return true;
11744 
11745 				case 'mceToggleEditor':
11746 					if (!ed) {
11747 						t.execCommand('mceAddControl', 0, v);
11748 						return true;
11749 					}
11750 
11751 					if (ed.isHidden())
11752 						ed.show();
11753 					else
11754 						ed.hide();
11755 
11756 					return true;
11757 			}
11758 
11759 			// Run command on active editor
11760 			if (t.activeEditor)
11761 				return t.activeEditor.execCommand(c, u, v);
11762 
11763 			return false;
11764 		},
11765 
11766 		execInstanceCommand : function(id, c, u, v) {
11767 			var ed = this.get(id);
11768 
11769 			if (ed)
11770 				return ed.execCommand(c, u, v);
11771 
11772 			return false;
11773 		},
11774 
11775 		triggerSave : function() {
11776 			each(this.editors, function(e) {
11777 				e.save();
11778 			});
11779 		},
11780 
11781 		addI18n : function(p, o) {
11782 			var lo, i18n = this.i18n;
11783 
11784 			if (!tinymce.is(p, 'string')) {
11785 				each(p, function(o, lc) {
11786 					each(o, function(o, g) {
11787 						each(o, function(o, k) {
11788 							if (g === 'common')
11789 								i18n[lc + '.' + k] = o;
11790 							else
11791 								i18n[lc + '.' + g + '.' + k] = o;
11792 						});
11793 					});
11794 				});
11795 			} else {
11796 				each(o, function(o, k) {
11797 					i18n[p + '.' + k] = o;
11798 				});
11799 			}
11800 		},
11801 
11802 		// Private methods
11803 
11804 		_setActive : function(editor) {
11805 			this.selectedInstance = this.activeEditor = editor;
11806 		}
11807 	});
11808 })(tinymce);
11809 
11810 (function(tinymce) {
11811 	// Shorten these names
11812 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
11813 		each = tinymce.each, isGecko = tinymce.isGecko,
11814 		isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
11815 		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
11816 		explode = tinymce.explode;
11817 
11818 	tinymce.create('tinymce.Editor', {
11819 		Editor : function(id, settings) {
11820 			var self = this, TRUE = true;
11821 
11822 			self.settings = settings = extend({
11823 				id : id,
11824 				language : 'en',
11825 				theme : 'advanced',
11826 				skin : 'default',
11827 				delta_width : 0,
11828 				delta_height : 0,
11829 				popup_css : '',
11830 				plugins : '',
11831 				document_base_url : tinymce.documentBaseURL,
11832 				add_form_submit_trigger : TRUE,
11833 				submit_patch : TRUE,
11834 				add_unload_trigger : TRUE,
11835 				convert_urls : TRUE,
11836 				relative_urls : TRUE,
11837 				remove_script_host : TRUE,
11838 				table_inline_editing : false,
11839 				object_resizing : TRUE,
11840 				accessibility_focus : TRUE,
11841 				doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
11842 				visual : TRUE,
11843 				font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
11844 				font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
11845 				apply_source_formatting : TRUE,
11846 				directionality : 'ltr',
11847 				forced_root_block : 'p',
11848 				hidden_input : TRUE,
11849 				padd_empty_editor : TRUE,
11850 				render_ui : TRUE,
11851 				indentation : '30px',
11852 				fix_table_elements : TRUE,
11853 				inline_styles : TRUE,
11854 				convert_fonts_to_spans : TRUE,
11855 				indent : 'simple',
11856 				indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
11857 				indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
11858 				validate : TRUE,
11859 				entity_encoding : 'named',
11860 				url_converter : self.convertURL,
11861 				url_converter_scope : self,
11862 				ie7_compat : TRUE
11863 			}, settings);
11864 
11865 			self.id = self.editorId = id;
11866 
11867 			self.isNotDirty = false;
11868 
11869 			self.plugins = {};
11870 
11871 			self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, {
11872 				base_uri : tinyMCE.baseURI
11873 			});
11874 
11875 			self.baseURI = tinymce.baseURI;
11876 
11877 			self.contentCSS = [];
11878 
11879 			self.contentStyles = [];
11880 
11881 			// Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
11882 			self.setupEvents();
11883 
11884 			// Internal command handler objects
11885 			self.execCommands = {};
11886 			self.queryStateCommands = {};
11887 			self.queryValueCommands = {};
11888 
11889 			// Call setup
11890 			self.execCallback('setup', self);
11891 		},
11892 
11893 		render : function(nst) {
11894 			var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
11895 
11896 			// Page is not loaded yet, wait for it
11897 			if (!Event.domLoaded) {
11898 				Event.add(window, 'ready', function() {
11899 					t.render();
11900 				});
11901 				return;
11902 			}
11903 
11904 			tinyMCE.settings = s;
11905 
11906 			// Element not found, then skip initialization
11907 			if (!t.getElement())
11908 				return;
11909 
11910 			// Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 
11911 			// here since the browser says it has contentEditable support but there is no visible caret.
11912 			if (tinymce.isIDevice && !tinymce.isIOS5)
11913 				return;
11914 
11915 			// Add hidden input for non input elements inside form elements
11916 			if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
11917 				DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
11918 
11919 			// Hide target element early to prevent content flashing
11920 			if (!s.content_editable) {
11921 				t.orgVisibility = t.getElement().style.visibility;
11922 				t.getElement().style.visibility = 'hidden';
11923 			}
11924 
11925 			if (tinymce.WindowManager)
11926 				t.windowManager = new tinymce.WindowManager(t);
11927 
11928 			if (s.encoding == 'xml') {
11929 				t.onGetContent.add(function(ed, o) {
11930 					if (o.save)
11931 						o.content = DOM.encode(o.content);
11932 				});
11933 			}
11934 
11935 			if (s.add_form_submit_trigger) {
11936 				t.onSubmit.addToTop(function() {
11937 					if (t.initialized) {
11938 						t.save();
11939 						t.isNotDirty = 1;
11940 					}
11941 				});
11942 			}
11943 
11944 			if (s.add_unload_trigger) {
11945 				t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
11946 					if (t.initialized && !t.destroyed && !t.isHidden())
11947 						t.save({format : 'raw', no_events : true});
11948 				});
11949 			}
11950 
11951 			tinymce.addUnload(t.destroy, t);
11952 
11953 			if (s.submit_patch) {
11954 				t.onBeforeRenderUI.add(function() {
11955 					var n = t.getElement().form;
11956 
11957 					if (!n)
11958 						return;
11959 
11960 					// Already patched
11961 					if (n._mceOldSubmit)
11962 						return;
11963 
11964 					// Check page uses id="submit" or name="submit" for it's submit button
11965 					if (!n.submit.nodeType && !n.submit.length) {
11966 						t.formElement = n;
11967 						n._mceOldSubmit = n.submit;
11968 						n.submit = function() {
11969 							// Save all instances
11970 							tinymce.triggerSave();
11971 							t.isNotDirty = 1;
11972 
11973 							return t.formElement._mceOldSubmit(t.formElement);
11974 						};
11975 					}
11976 
11977 					n = null;
11978 				});
11979 			}
11980 
11981 			// Load scripts
11982 			function loadScripts() {
11983 				if (s.language && s.language_load !== false)
11984 					sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
11985 
11986 				if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
11987 					ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
11988 
11989 				each(explode(s.plugins), function(p) {
11990 					if (p &&!PluginManager.urls[p]) {
11991 						if (p.charAt(0) == '-') {
11992 							p = p.substr(1, p.length);
11993 							var dependencies = PluginManager.dependencies(p);
11994 							each(dependencies, function(dep) {
11995 								var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
11996 								dep = PluginManager.createUrl(defaultSettings, dep);
11997 								PluginManager.load(dep.resource, dep);
11998 							});
11999 						} else {
12000 							// Skip safari plugin, since it is removed as of 3.3b1
12001 							if (p == 'safari') {
12002 								return;
12003 							}
12004 							PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
12005 						}
12006 					}
12007 				});
12008 
12009 				// Init when que is loaded
12010 				sl.loadQueue(function() {
12011 					if (!t.removed)
12012 						t.init();
12013 				});
12014 			};
12015 
12016 			loadScripts();
12017 		},
12018 
12019 		init : function() {
12020 			var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
12021 
12022 			tinymce.add(t);
12023 
12024 			s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
12025 
12026 			if (s.theme) {
12027 				if (typeof s.theme != "function") {
12028 					s.theme = s.theme.replace(/-/, '');
12029 					o = ThemeManager.get(s.theme);
12030 					t.theme = new o();
12031 
12032 					if (t.theme.init)
12033 						t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
12034 				} else {
12035 					t.theme = s.theme;
12036 				}
12037 			}
12038 
12039 			function initPlugin(p) {
12040 				var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
12041 				if (c && tinymce.inArray(initializedPlugins,p) === -1) {
12042 					each(PluginManager.dependencies(p), function(dep){
12043 						initPlugin(dep);
12044 					});
12045 					po = new c(t, u);
12046 
12047 					t.plugins[p] = po;
12048 
12049 					if (po.init) {
12050 						po.init(t, u);
12051 						initializedPlugins.push(p);
12052 					}
12053 				}
12054 			}
12055 			
12056 			// Create all plugins
12057 			each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
12058 
12059 			// Setup popup CSS path(s)
12060 			if (s.popup_css !== false) {
12061 				if (s.popup_css)
12062 					s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
12063 				else
12064 					s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
12065 			}
12066 
12067 			if (s.popup_css_add)
12068 				s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
12069 
12070 			t.controlManager = new tinymce.ControlManager(t);
12071 
12072 			// Enables users to override the control factory
12073 			t.onBeforeRenderUI.dispatch(t, t.controlManager);
12074 
12075 			// Measure box
12076 			if (s.render_ui && t.theme) {
12077 				t.orgDisplay = e.style.display;
12078 
12079 				if (typeof s.theme != "function") {
12080 					w = s.width || e.style.width || e.offsetWidth;
12081 					h = s.height || e.style.height || e.offsetHeight;
12082 					mh = s.min_height || 100;
12083 					re = /^[0-9\.]+(|px)$/i;
12084 
12085 					if (re.test('' + w))
12086 						w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100);
12087 
12088 					if (re.test('' + h))
12089 						h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh);
12090 
12091 					// Render UI
12092 					o = t.theme.renderUI({
12093 						targetNode : e,
12094 						width : w,
12095 						height : h,
12096 						deltaWidth : s.delta_width,
12097 						deltaHeight : s.delta_height
12098 					});
12099 
12100 					// Resize editor
12101 					DOM.setStyles(o.sizeContainer || o.editorContainer, {
12102 						width : w,
12103 						height : h
12104 					});
12105 
12106 					h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
12107 					if (h < mh)
12108 						h = mh;
12109 				} else {
12110 					o = s.theme(t, e);
12111 
12112 					// Convert element type to id:s
12113 					if (o.editorContainer.nodeType) {
12114 						o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent";
12115 					}
12116 
12117 					// Convert element type to id:s
12118 					if (o.iframeContainer.nodeType) {
12119 						o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer";
12120 					}
12121 
12122 					// Use specified iframe height or the targets offsetHeight
12123 					h = o.iframeHeight || e.offsetHeight;
12124 
12125 					// Store away the selection when it's changed to it can be restored later with a editor.focus() call
12126 					if (isIE) {
12127 						t.onInit.add(function(ed) {
12128 							ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() {
12129 								ed.lastIERng = ed.selection.getRng();
12130 							});
12131 						});
12132 					}
12133 				}
12134 
12135 				t.editorContainer = o.editorContainer;
12136 			}
12137 
12138 			// Load specified content CSS last
12139 			if (s.content_css) {
12140 				each(explode(s.content_css), function(u) {
12141 					t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
12142 				});
12143 			}
12144 
12145 			// Content editable mode ends here
12146 			if (s.content_editable) {
12147 				e = n = o = null; // Fix IE leak
12148 				return t.initContentBody();
12149 			}
12150 
12151 			// User specified a document.domain value
12152 			if (document.domain && location.hostname != document.domain)
12153 				tinymce.relaxedDomain = document.domain;
12154 
12155 			t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
12156 
12157 			// We only need to override paths if we have to
12158 			// IE has a bug where it remove site absolute urls to relative ones if this is specified
12159 			if (s.document_base_url != tinymce.documentBaseURL)
12160 				t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
12161 
12162 			// IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
12163 			if (s.ie7_compat)
12164 				t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
12165 			else
12166 				t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
12167 
12168 			t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
12169 
12170 			// Load the CSS by injecting them into the HTML this will reduce "flicker"
12171 			for (i = 0; i < t.contentCSS.length; i++) {
12172 				t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
12173 			}
12174 
12175 			t.contentCSS = [];
12176 
12177 			bi = s.body_id || 'tinymce';
12178 			if (bi.indexOf('=') != -1) {
12179 				bi = t.getParam('body_id', '', 'hash');
12180 				bi = bi[t.id] || bi;
12181 			}
12182 
12183 			bc = s.body_class || '';
12184 			if (bc.indexOf('=') != -1) {
12185 				bc = t.getParam('body_class', '', 'hash');
12186 				bc = bc[t.id] || '';
12187 			}
12188 
12189 			t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>';
12190 
12191 			// Domain relaxing enabled, then set document domain
12192 			if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
12193 				// We need to write the contents here in IE since multiple writes messes up refresh button and back button
12194 				u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()';
12195 			}
12196 
12197 			// Create iframe
12198 			// TODO: ACC add the appropriate description on this.
12199 			n = DOM.add(o.iframeContainer, 'iframe', { 
12200 				id : t.id + "_ifr",
12201 				src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
12202 				frameBorder : '0',
12203 				allowTransparency : "true",
12204 				title : s.aria_label,
12205 				style : {
12206 					width : '100%',
12207 					height : h,
12208 					display : 'block' // Important for Gecko to render the iframe correctly
12209 				}
12210 			});
12211 
12212 			t.contentAreaContainer = o.iframeContainer;
12213 
12214 			if (o.editorContainer) {
12215 				DOM.get(o.editorContainer).style.display = t.orgDisplay;
12216 			}
12217 
12218 			// Restore visibility on target element
12219 			e.style.visibility = t.orgVisibility;
12220 
12221 			DOM.get(t.id).style.display = 'none';
12222 			DOM.setAttrib(t.id, 'aria-hidden', true);
12223 
12224 			if (!tinymce.relaxedDomain || !u)
12225 				t.initContentBody();
12226 
12227 			e = n = o = null; // Cleanup
12228 		},
12229 
12230 		initContentBody : function() {
12231 			var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText;
12232 
12233 			// Setup iframe body
12234 			if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) {
12235 				doc.open();
12236 				doc.write(self.iframeHTML);
12237 				doc.close();
12238 
12239 				if (tinymce.relaxedDomain)
12240 					doc.domain = tinymce.relaxedDomain;
12241 			}
12242 
12243 			if (settings.content_editable) {
12244 				DOM.addClass(targetElm, 'mceContentBody');
12245 				self.contentDocument = doc = settings.content_document || document;
12246 				self.contentWindow = settings.content_window || window;
12247 				self.bodyElement = targetElm;
12248 
12249 				// Prevent leak in IE
12250 				settings.content_document = settings.content_window = null;
12251 			}
12252 
12253 			// It will not steal focus while setting contentEditable
12254 			body = self.getBody();
12255 			body.disabled = true;
12256 
12257 			if (!settings.readonly)
12258 				body.contentEditable = self.getParam('content_editable_state', true);
12259 
12260 			body.disabled = false;
12261 
12262 			self.schema = new tinymce.html.Schema(settings);
12263 
12264 			self.dom = new tinymce.dom.DOMUtils(doc, {
12265 				keep_values : true,
12266 				url_converter : self.convertURL,
12267 				url_converter_scope : self,
12268 				hex_colors : settings.force_hex_style_colors,
12269 				class_filter : settings.class_filter,
12270 				update_styles : true,
12271 				root_element : settings.content_editable ? self.id : null,
12272 				schema : self.schema
12273 			});
12274 
12275 			self.parser = new tinymce.html.DomParser(settings, self.schema);
12276 
12277 			// Convert src and href into data-mce-src, data-mce-href and data-mce-style
12278 			self.parser.addAttributeFilter('src,href,style', function(nodes, name) {
12279 				var i = nodes.length, node, dom = self.dom, value, internalName;
12280 
12281 				while (i--) {
12282 					node = nodes[i];
12283 					value = node.attr(name);
12284 					internalName = 'data-mce-' + name;
12285 
12286 					// Add internal attribute if we need to we don't on a refresh of the document
12287 					if (!node.attributes.map[internalName]) {	
12288 						if (name === "style")
12289 							node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
12290 						else
12291 							node.attr(internalName, self.convertURL(value, name, node.name));
12292 					}
12293 				}
12294 			});
12295 
12296 			// Keep scripts from executing
12297 			self.parser.addNodeFilter('script', function(nodes, name) {
12298 				var i = nodes.length, node;
12299 
12300 				while (i--) {
12301 					node = nodes[i];
12302 					node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
12303 				}
12304 			});
12305 
12306 			self.parser.addNodeFilter('#cdata', function(nodes, name) {
12307 				var i = nodes.length, node;
12308 
12309 				while (i--) {
12310 					node = nodes[i];
12311 					node.type = 8;
12312 					node.name = '#comment';
12313 					node.value = '[CDATA[' + node.value + ']]';
12314 				}
12315 			});
12316 
12317 			self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
12318 				var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
12319 
12320 				while (i--) {
12321 					node = nodes[i];
12322 
12323 					if (node.isEmpty(nonEmptyElements))
12324 						node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
12325 				}
12326 			});
12327 
12328 			self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema);
12329 
12330 			self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self);
12331 
12332 			self.formatter = new tinymce.Formatter(self);
12333 
12334 			self.undoManager = new tinymce.UndoManager(self);
12335 
12336 			self.forceBlocks = new tinymce.ForceBlocks(self);
12337 			self.enterKey = new tinymce.EnterKey(self);
12338 			self.editorCommands = new tinymce.EditorCommands(self);
12339 
12340 			self.onExecCommand.add(function(editor, command) {
12341 				// Don't refresh the select lists until caret move
12342 				if (!/^(FontName|FontSize)$/.test(command))
12343 					self.nodeChanged();
12344 			});
12345 
12346 			// Pass through
12347 			self.serializer.onPreProcess.add(function(se, o) {
12348 				return self.onPreProcess.dispatch(self, o, se);
12349 			});
12350 
12351 			self.serializer.onPostProcess.add(function(se, o) {
12352 				return self.onPostProcess.dispatch(self, o, se);
12353 			});
12354 
12355 			self.onPreInit.dispatch(self);
12356 
12357 			if (!settings.browser_spellcheck && !settings.gecko_spellcheck)
12358 				doc.body.spellcheck = false;
12359 
12360 			if (!settings.readonly) {
12361 				self.bindNativeEvents();
12362 			}
12363 
12364 			self.controlManager.onPostRender.dispatch(self, self.controlManager);
12365 			self.onPostRender.dispatch(self);
12366 
12367 			self.quirks = tinymce.util.Quirks(self);
12368 
12369 			if (settings.directionality)
12370 				body.dir = settings.directionality;
12371 
12372 			if (settings.nowrap)
12373 				body.style.whiteSpace = "nowrap";
12374 
12375 			if (settings.protect) {
12376 				self.onBeforeSetContent.add(function(ed, o) {
12377 					each(settings.protect, function(pattern) {
12378 						o.content = o.content.replace(pattern, function(str) {
12379 							return '<!--mce:protected ' + escape(str) + '-->';
12380 						});
12381 					});
12382 				});
12383 			}
12384 
12385 			// Add visual aids when new contents is added
12386 			self.onSetContent.add(function() {
12387 				self.addVisual(self.getBody());
12388 			});
12389 
12390 			// Remove empty contents
12391 			if (settings.padd_empty_editor) {
12392 				self.onPostProcess.add(function(ed, o) {
12393 					o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
12394 				});
12395 			}
12396 
12397 			self.load({initial : true, format : 'html'});
12398 			self.startContent = self.getContent({format : 'raw'});
12399 
12400 			self.initialized = true;
12401 
12402 			self.onInit.dispatch(self);
12403 			self.execCallback('setupcontent_callback', self.id, body, doc);
12404 			self.execCallback('init_instance_callback', self);
12405 			self.focus(true);
12406 			self.nodeChanged({initial : true});
12407 
12408 			// Add editor specific CSS styles
12409 			if (self.contentStyles.length > 0) {
12410 				contentCssText = '';
12411 
12412 				each(self.contentStyles, function(style) {
12413 					contentCssText += style + "\r\n";
12414 				});
12415 
12416 				self.dom.addStyle(contentCssText);
12417 			}
12418 
12419 			// Load specified content CSS last
12420 			each(self.contentCSS, function(url) {
12421 				self.dom.loadCSS(url);
12422 			});
12423 
12424 			// Handle auto focus
12425 			if (settings.auto_focus) {
12426 				setTimeout(function () {
12427 					var ed = tinymce.get(settings.auto_focus);
12428 
12429 					ed.selection.select(ed.getBody(), 1);
12430 					ed.selection.collapse(1);
12431 					ed.getBody().focus();
12432 					ed.getWin().focus();
12433 				}, 100);
12434 			}
12435 
12436 			// Clean up references for IE
12437 			targetElm = doc = body = null;
12438 		},
12439 
12440 		focus : function(skip_focus) {
12441 			var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body;
12442 
12443 			if (!skip_focus) {
12444 				if (self.lastIERng) {
12445 					selection.setRng(self.lastIERng);
12446 				}
12447 
12448 				// Get selected control element
12449 				ieRng = selection.getRng();
12450 				if (ieRng.item) {
12451 					controlElm = ieRng.item(0);
12452 				}
12453 
12454 				self._refreshContentEditable();
12455 
12456 				// Focus the window iframe
12457 				if (!contentEditable) {
12458 					self.getWin().focus();
12459 				}
12460 
12461 				// Focus the body as well since it's contentEditable
12462 				if (tinymce.isGecko || contentEditable) {
12463 					body = self.getBody();
12464 
12465 					// Check for setActive since it doesn't scroll to the element
12466 					if (body.setActive) {
12467 						body.setActive();
12468 					} else {
12469 						body.focus();
12470 					}
12471 
12472 					if (contentEditable) {
12473 						selection.normalize();
12474 					}
12475 				}
12476 
12477 				// Restore selected control element
12478 				// This is needed when for example an image is selected within a
12479 				// layer a call to focus will then remove the control selection
12480 				if (controlElm && controlElm.ownerDocument == doc) {
12481 					ieRng = doc.body.createControlRange();
12482 					ieRng.addElement(controlElm);
12483 					ieRng.select();
12484 				}
12485 			}
12486 
12487 			if (tinymce.activeEditor != self) {
12488 				if ((oed = tinymce.activeEditor) != null)
12489 					oed.onDeactivate.dispatch(oed, self);
12490 
12491 				self.onActivate.dispatch(self, oed);
12492 			}
12493 
12494 			tinymce._setActive(self);
12495 		},
12496 
12497 		execCallback : function(n) {
12498 			var t = this, f = t.settings[n], s;
12499 
12500 			if (!f)
12501 				return;
12502 
12503 			// Look through lookup
12504 			if (t.callbackLookup && (s = t.callbackLookup[n])) {
12505 				f = s.func;
12506 				s = s.scope;
12507 			}
12508 
12509 			if (is(f, 'string')) {
12510 				s = f.replace(/\.\w+$/, '');
12511 				s = s ? tinymce.resolve(s) : 0;
12512 				f = tinymce.resolve(f);
12513 				t.callbackLookup = t.callbackLookup || {};
12514 				t.callbackLookup[n] = {func : f, scope : s};
12515 			}
12516 
12517 			return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
12518 		},
12519 
12520 		translate : function(s) {
12521 			var c = this.settings.language || 'en', i18n = tinymce.i18n;
12522 
12523 			if (!s)
12524 				return '';
12525 
12526 			return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) {
12527 				return i18n[c + '.' + b] || '{#' + b + '}';
12528 			});
12529 		},
12530 
12531 		getLang : function(n, dv) {
12532 			return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
12533 		},
12534 
12535 		getParam : function(n, dv, ty) {
12536 			var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
12537 
12538 			if (ty === 'hash') {
12539 				o = {};
12540 
12541 				if (is(v, 'string')) {
12542 					each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
12543 						v = v.split('=');
12544 
12545 						if (v.length > 1)
12546 							o[tr(v[0])] = tr(v[1]);
12547 						else
12548 							o[tr(v[0])] = tr(v);
12549 					});
12550 				} else
12551 					o = v;
12552 
12553 				return o;
12554 			}
12555 
12556 			return v;
12557 		},
12558 
12559 		nodeChanged : function(o) {
12560 			var self = this, selection = self.selection, node;
12561 
12562 			// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
12563 			if (self.initialized) {
12564 				o = o || {};
12565 
12566 				// Get start node
12567 				node = selection.getStart() || self.getBody();
12568 				node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state
12569 
12570 				// Get parents and add them to object
12571 				o.parents = [];
12572 				self.dom.getParent(node, function(node) {
12573 					if (node.nodeName == 'BODY')
12574 						return true;
12575 
12576 					o.parents.push(node);
12577 				});
12578 
12579 				self.onNodeChange.dispatch(
12580 					self,
12581 					o ? o.controlManager || self.controlManager : self.controlManager,
12582 					node,
12583 					selection.isCollapsed(),
12584 					o
12585 				);
12586 			}
12587 		},
12588 
12589 		addButton : function(name, settings) {
12590 			var self = this;
12591 
12592 			self.buttons = self.buttons || {};
12593 			self.buttons[name] = settings;
12594 		},
12595 
12596 		addCommand : function(name, callback, scope) {
12597 			this.execCommands[name] = {func : callback, scope : scope || this};
12598 		},
12599 
12600 		addQueryStateHandler : function(name, callback, scope) {
12601 			this.queryStateCommands[name] = {func : callback, scope : scope || this};
12602 		},
12603 
12604 		addQueryValueHandler : function(name, callback, scope) {
12605 			this.queryValueCommands[name] = {func : callback, scope : scope || this};
12606 		},
12607 
12608 		addShortcut : function(pa, desc, cmd_func, sc) {
12609 			var t = this, c;
12610 
12611 			if (t.settings.custom_shortcuts === false)
12612 				return false;
12613 
12614 			t.shortcuts = t.shortcuts || {};
12615 
12616 			if (is(cmd_func, 'string')) {
12617 				c = cmd_func;
12618 
12619 				cmd_func = function() {
12620 					t.execCommand(c, false, null);
12621 				};
12622 			}
12623 
12624 			if (is(cmd_func, 'object')) {
12625 				c = cmd_func;
12626 
12627 				cmd_func = function() {
12628 					t.execCommand(c[0], c[1], c[2]);
12629 				};
12630 			}
12631 
12632 			each(explode(pa), function(pa) {
12633 				var o = {
12634 					func : cmd_func,
12635 					scope : sc || this,
12636 					desc : t.translate(desc),
12637 					alt : false,
12638 					ctrl : false,
12639 					shift : false
12640 				};
12641 
12642 				each(explode(pa, '+'), function(v) {
12643 					switch (v) {
12644 						case 'alt':
12645 						case 'ctrl':
12646 						case 'shift':
12647 							o[v] = true;
12648 							break;
12649 
12650 						default:
12651 							o.charCode = v.charCodeAt(0);
12652 							o.keyCode = v.toUpperCase().charCodeAt(0);
12653 					}
12654 				});
12655 
12656 				t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
12657 			});
12658 
12659 			return true;
12660 		},
12661 
12662 		execCommand : function(cmd, ui, val, a) {
12663 			var t = this, s = 0, o, st;
12664 
12665 			if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
12666 				t.focus();
12667 
12668 			a = extend({}, a);
12669 			t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a);
12670 			if (a.terminate)
12671 				return false;
12672 
12673 			// Command callback
12674 			if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
12675 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
12676 				return true;
12677 			}
12678 
12679 			// Registred commands
12680 			if (o = t.execCommands[cmd]) {
12681 				st = o.func.call(o.scope, ui, val);
12682 
12683 				// Fall through on true
12684 				if (st !== true) {
12685 					t.onExecCommand.dispatch(t, cmd, ui, val, a);
12686 					return st;
12687 				}
12688 			}
12689 
12690 			// Plugin commands
12691 			each(t.plugins, function(p) {
12692 				if (p.execCommand && p.execCommand(cmd, ui, val)) {
12693 					t.onExecCommand.dispatch(t, cmd, ui, val, a);
12694 					s = 1;
12695 					return false;
12696 				}
12697 			});
12698 
12699 			if (s)
12700 				return true;
12701 
12702 			// Theme commands
12703 			if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
12704 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
12705 				return true;
12706 			}
12707 
12708 			// Editor commands
12709 			if (t.editorCommands.execCommand(cmd, ui, val)) {
12710 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
12711 				return true;
12712 			}
12713 
12714 			// Browser commands
12715 			t.getDoc().execCommand(cmd, ui, val);
12716 			t.onExecCommand.dispatch(t, cmd, ui, val, a);
12717 		},
12718 
12719 		queryCommandState : function(cmd) {
12720 			var t = this, o, s;
12721 
12722 			// Is hidden then return undefined
12723 			if (t._isHidden())
12724 				return;
12725 
12726 			// Registred commands
12727 			if (o = t.queryStateCommands[cmd]) {
12728 				s = o.func.call(o.scope);
12729 
12730 				// Fall though on true
12731 				if (s !== true)
12732 					return s;
12733 			}
12734 
12735 			// Registred commands
12736 			o = t.editorCommands.queryCommandState(cmd);
12737 			if (o !== -1)
12738 				return o;
12739 
12740 			// Browser commands
12741 			try {
12742 				return this.getDoc().queryCommandState(cmd);
12743 			} catch (ex) {
12744 				// Fails sometimes see bug: 1896577
12745 			}
12746 		},
12747 
12748 		queryCommandValue : function(c) {
12749 			var t = this, o, s;
12750 
12751 			// Is hidden then return undefined
12752 			if (t._isHidden())
12753 				return;
12754 
12755 			// Registred commands
12756 			if (o = t.queryValueCommands[c]) {
12757 				s = o.func.call(o.scope);
12758 
12759 				// Fall though on true
12760 				if (s !== true)
12761 					return s;
12762 			}
12763 
12764 			// Registred commands
12765 			o = t.editorCommands.queryCommandValue(c);
12766 			if (is(o))
12767 				return o;
12768 
12769 			// Browser commands
12770 			try {
12771 				return this.getDoc().queryCommandValue(c);
12772 			} catch (ex) {
12773 				// Fails sometimes see bug: 1896577
12774 			}
12775 		},
12776 
12777 		show : function() {
12778 			var self = this;
12779 
12780 			DOM.show(self.getContainer());
12781 			DOM.hide(self.id);
12782 			self.load();
12783 		},
12784 
12785 		hide : function() {
12786 			var self = this, doc = self.getDoc();
12787 
12788 			// Fixed bug where IE has a blinking cursor left from the editor
12789 			if (isIE && doc)
12790 				doc.execCommand('SelectAll');
12791 
12792 			// We must save before we hide so Safari doesn't crash
12793 			self.save();
12794 			DOM.hide(self.getContainer());
12795 			DOM.setStyle(self.id, 'display', self.orgDisplay);
12796 		},
12797 
12798 		isHidden : function() {
12799 			return !DOM.isHidden(this.id);
12800 		},
12801 
12802 		setProgressState : function(b, ti, o) {
12803 			this.onSetProgressState.dispatch(this, b, ti, o);
12804 
12805 			return b;
12806 		},
12807 
12808 		load : function(o) {
12809 			var t = this, e = t.getElement(), h;
12810 
12811 			if (e) {
12812 				o = o || {};
12813 				o.load = true;
12814 
12815 				// Double encode existing entities in the value
12816 				h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
12817 				o.element = e;
12818 
12819 				if (!o.no_events)
12820 					t.onLoadContent.dispatch(t, o);
12821 
12822 				o.element = e = null;
12823 
12824 				return h;
12825 			}
12826 		},
12827 
12828 		save : function(o) {
12829 			var t = this, e = t.getElement(), h, f;
12830 
12831 			if (!e || !t.initialized)
12832 				return;
12833 
12834 			o = o || {};
12835 			o.save = true;
12836 
12837 			o.element = e;
12838 			h = o.content = t.getContent(o);
12839 
12840 			if (!o.no_events)
12841 				t.onSaveContent.dispatch(t, o);
12842 
12843 			h = o.content;
12844 
12845 			if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
12846 				e.innerHTML = h;
12847 
12848 				// Update hidden form element
12849 				if (f = DOM.getParent(t.id, 'form')) {
12850 					each(f.elements, function(e) {
12851 						if (e.name == t.id) {
12852 							e.value = h;
12853 							return false;
12854 						}
12855 					});
12856 				}
12857 			} else
12858 				e.value = h;
12859 
12860 			o.element = e = null;
12861 
12862 			return h;
12863 		},
12864 
12865 		setContent : function(content, args) {
12866 			var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
12867 
12868 			// Setup args object
12869 			args = args || {};
12870 			args.format = args.format || 'html';
12871 			args.set = true;
12872 			args.content = content;
12873 
12874 			// Do preprocessing
12875 			if (!args.no_events)
12876 				self.onBeforeSetContent.dispatch(self, args);
12877 
12878 			content = args.content;
12879 
12880 			// Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
12881 			// It will also be impossible to place the caret in the editor unless there is a BR element present
12882 			if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
12883 				forcedRootBlockName = self.settings.forced_root_block;
12884 				if (forcedRootBlockName)
12885 					content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
12886 				else
12887 					content = '<br data-mce-bogus="1">';
12888 
12889 				body.innerHTML = content;
12890 				self.selection.select(body, true);
12891 				self.selection.collapse(true);
12892 				return;
12893 			}
12894 
12895 			// Parse and serialize the html
12896 			if (args.format !== 'raw') {
12897 				content = new tinymce.html.Serializer({}, self.schema).serialize(
12898 					self.parser.parse(content)
12899 				);
12900 			}
12901 
12902 			// Set the new cleaned contents to the editor
12903 			args.content = tinymce.trim(content);
12904 			self.dom.setHTML(body, args.content);
12905 
12906 			// Do post processing
12907 			if (!args.no_events)
12908 				self.onSetContent.dispatch(self, args);
12909 
12910 			// Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise
12911 			if (!self.settings.content_editable || document.activeElement === self.getBody()) {
12912 				self.selection.normalize();
12913 			}
12914 
12915 			return args.content;
12916 		},
12917 
12918 		getContent : function(args) {
12919 			var self = this, content;
12920 
12921 			// Setup args object
12922 			args = args || {};
12923 			args.format = args.format || 'html';
12924 			args.get = true;
12925 			args.getInner = true;
12926 
12927 			// Do preprocessing
12928 			if (!args.no_events)
12929 				self.onBeforeGetContent.dispatch(self, args);
12930 
12931 			// Get raw contents or by default the cleaned contents
12932 			if (args.format == 'raw')
12933 				content = self.getBody().innerHTML;
12934 			else
12935 				content = self.serializer.serialize(self.getBody(), args);
12936 
12937 			args.content = tinymce.trim(content);
12938 
12939 			// Do post processing
12940 			if (!args.no_events)
12941 				self.onGetContent.dispatch(self, args);
12942 
12943 			return args.content;
12944 		},
12945 
12946 		isDirty : function() {
12947 			var self = this;
12948 
12949 			return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
12950 		},
12951 
12952 		getContainer : function() {
12953 			var self = this;
12954 
12955 			if (!self.container)
12956 				self.container = DOM.get(self.editorContainer || self.id + '_parent');
12957 
12958 			return self.container;
12959 		},
12960 
12961 		getContentAreaContainer : function() {
12962 			return this.contentAreaContainer;
12963 		},
12964 
12965 		getElement : function() {
12966 			return DOM.get(this.settings.content_element || this.id);
12967 		},
12968 
12969 		getWin : function() {
12970 			var self = this, elm;
12971 
12972 			if (!self.contentWindow) {
12973 				elm = DOM.get(self.id + "_ifr");
12974 
12975 				if (elm)
12976 					self.contentWindow = elm.contentWindow;
12977 			}
12978 
12979 			return self.contentWindow;
12980 		},
12981 
12982 		getDoc : function() {
12983 			var self = this, win;
12984 
12985 			if (!self.contentDocument) {
12986 				win = self.getWin();
12987 
12988 				if (win)
12989 					self.contentDocument = win.document;
12990 			}
12991 
12992 			return self.contentDocument;
12993 		},
12994 
12995 		getBody : function() {
12996 			return this.bodyElement || this.getDoc().body;
12997 		},
12998 
12999 		convertURL : function(url, name, elm) {
13000 			var self = this, settings = self.settings;
13001 
13002 			// Use callback instead
13003 			if (settings.urlconverter_callback)
13004 				return self.execCallback('urlconverter_callback', url, elm, true, name);
13005 
13006 			// Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
13007 			if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0)
13008 				return url;
13009 
13010 			// Convert to relative
13011 			if (settings.relative_urls)
13012 				return self.documentBaseURI.toRelative(url);
13013 
13014 			// Convert to absolute
13015 			url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
13016 
13017 			return url;
13018 		},
13019 
13020 		addVisual : function(elm) {
13021 			var self = this, settings = self.settings, dom = self.dom, cls;
13022 
13023 			elm = elm || self.getBody();
13024 
13025 			if (!is(self.hasVisual))
13026 				self.hasVisual = settings.visual;
13027 
13028 			each(dom.select('table,a', elm), function(elm) {
13029 				var value;
13030 
13031 				switch (elm.nodeName) {
13032 					case 'TABLE':
13033 						cls = settings.visual_table_class || 'mceItemTable';
13034 						value = dom.getAttrib(elm, 'border');
13035 
13036 						if (!value || value == '0') {
13037 							if (self.hasVisual)
13038 								dom.addClass(elm, cls);
13039 							else
13040 								dom.removeClass(elm, cls);
13041 						}
13042 
13043 						return;
13044 
13045 					case 'A':
13046 						if (!dom.getAttrib(elm, 'href', false)) {
13047 							value = dom.getAttrib(elm, 'name') || elm.id;
13048 							cls = 'mceItemAnchor';
13049 
13050 							if (value) {
13051 								if (self.hasVisual)
13052 									dom.addClass(elm, cls);
13053 								else
13054 									dom.removeClass(elm, cls);
13055 							}
13056 						}
13057 
13058 						return;
13059 				}
13060 			});
13061 
13062 			self.onVisualAid.dispatch(self, elm, self.hasVisual);
13063 		},
13064 
13065 		remove : function() {
13066 			var self = this, elm = self.getContainer();
13067 
13068 			if (!self.removed) {
13069 				self.removed = 1; // Cancels post remove event execution
13070 				self.hide();
13071 
13072 				// Don't clear the window or document if content editable
13073 				// is enabled since other instances might still be present
13074 				if (!self.settings.content_editable) {
13075 					Event.unbind(self.getWin());
13076 					Event.unbind(self.getDoc());
13077 				}
13078 
13079 				Event.unbind(self.getBody());
13080 				Event.clear(elm);
13081 
13082 				self.execCallback('remove_instance_callback', self);
13083 				self.onRemove.dispatch(self);
13084 
13085 				// Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
13086 				self.onExecCommand.listeners = [];
13087 
13088 				tinymce.remove(self);
13089 				DOM.remove(elm);
13090 			}
13091 		},
13092 
13093 		destroy : function(s) {
13094 			var t = this;
13095 
13096 			// One time is enough
13097 			if (t.destroyed)
13098 				return;
13099 
13100 			// We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message
13101 			if (isGecko) {
13102 				Event.unbind(t.getDoc());
13103 				Event.unbind(t.getWin());
13104 				Event.unbind(t.getBody());
13105 			}
13106 
13107 			if (!s) {
13108 				tinymce.removeUnload(t.destroy);
13109 				tinyMCE.onBeforeUnload.remove(t._beforeUnload);
13110 
13111 				// Manual destroy
13112 				if (t.theme && t.theme.destroy)
13113 					t.theme.destroy();
13114 
13115 				// Destroy controls, selection and dom
13116 				t.controlManager.destroy();
13117 				t.selection.destroy();
13118 				t.dom.destroy();
13119 			}
13120 
13121 			if (t.formElement) {
13122 				t.formElement.submit = t.formElement._mceOldSubmit;
13123 				t.formElement._mceOldSubmit = null;
13124 			}
13125 
13126 			t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
13127 
13128 			if (t.selection)
13129 				t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
13130 
13131 			t.destroyed = 1;
13132 		},
13133 
13134 		// Internal functions
13135 
13136 		_refreshContentEditable : function() {
13137 			var self = this, body, parent;
13138 
13139 			// Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
13140 			if (self._isHidden()) {
13141 				body = self.getBody();
13142 				parent = body.parentNode;
13143 
13144 				parent.removeChild(body);
13145 				parent.appendChild(body);
13146 
13147 				body.focus();
13148 			}
13149 		},
13150 
13151 		_isHidden : function() {
13152 			var s;
13153 
13154 			if (!isGecko)
13155 				return 0;
13156 
13157 			// Weird, wheres that cursor selection?
13158 			s = this.selection.getSel();
13159 			return (!s || !s.rangeCount || s.rangeCount === 0);
13160 		}
13161 	});
13162 })(tinymce);
13163 (function(tinymce) {
13164 	var each = tinymce.each;
13165 
13166 	tinymce.Editor.prototype.setupEvents = function() {
13167 		var self = this, settings = self.settings;
13168 
13169 		// Add events to the editor
13170 		each([
13171 			'onPreInit',
13172 
13173 			'onBeforeRenderUI',
13174 
13175 			'onPostRender',
13176 
13177 			'onLoad',
13178 
13179 			'onInit',
13180 
13181 			'onRemove',
13182 
13183 			'onActivate',
13184 
13185 			'onDeactivate',
13186 
13187 			'onClick',
13188 
13189 			'onEvent',
13190 
13191 			'onMouseUp',
13192 
13193 			'onMouseDown',
13194 
13195 			'onDblClick',
13196 
13197 			'onKeyDown',
13198 
13199 			'onKeyUp',
13200 
13201 			'onKeyPress',
13202 
13203 			'onContextMenu',
13204 
13205 			'onSubmit',
13206 
13207 			'onReset',
13208 
13209 			'onPaste',
13210 
13211 			'onPreProcess',
13212 
13213 			'onPostProcess',
13214 
13215 			'onBeforeSetContent',
13216 
13217 			'onBeforeGetContent',
13218 
13219 			'onSetContent',
13220 
13221 			'onGetContent',
13222 
13223 			'onLoadContent',
13224 
13225 			'onSaveContent',
13226 
13227 			'onNodeChange',
13228 
13229 			'onChange',
13230 
13231 			'onBeforeExecCommand',
13232 
13233 			'onExecCommand',
13234 
13235 			'onUndo',
13236 
13237 			'onRedo',
13238 
13239 			'onVisualAid',
13240 
13241 			'onSetProgressState',
13242 
13243 			'onSetAttrib'
13244 		], function(name) {
13245 			self[name] = new tinymce.util.Dispatcher(self);
13246 		});
13247 
13248 		// Handle legacy cleanup_callback option
13249 		if (settings.cleanup_callback) {
13250 			self.onBeforeSetContent.add(function(ed, o) {
13251 				o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
13252 			});
13253 
13254 			self.onPreProcess.add(function(ed, o) {
13255 				if (o.set)
13256 					ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
13257 
13258 				if (o.get)
13259 					ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
13260 			});
13261 
13262 			self.onPostProcess.add(function(ed, o) {
13263 				if (o.set)
13264 					o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
13265 
13266 				if (o.get)						
13267 					o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
13268 			});
13269 		}
13270 
13271 		// Handle legacy save_callback option
13272 		if (settings.save_callback) {
13273 			self.onGetContent.add(function(ed, o) {
13274 				if (o.save)
13275 					o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
13276 			});
13277 		}
13278 
13279 		// Handle legacy handle_event_callback option
13280 		if (settings.handle_event_callback) {
13281 			self.onEvent.add(function(ed, e, o) {
13282 				if (self.execCallback('handle_event_callback', e, ed, o) === false) {
13283 					e.preventDefault();
13284 					e.stopPropagation();
13285 				}
13286 			});
13287 		}
13288 
13289 		// Handle legacy handle_node_change_callback option
13290 		if (settings.handle_node_change_callback) {
13291 			self.onNodeChange.add(function(ed, cm, n) {
13292 				ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed());
13293 			});
13294 		}
13295 
13296 		// Handle legacy save_callback option
13297 		if (settings.save_callback) {
13298 			self.onSaveContent.add(function(ed, o) {
13299 				var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
13300 
13301 				if (h)
13302 					o.content = h;
13303 			});
13304 		}
13305 
13306 		// Handle legacy onchange_callback option
13307 		if (settings.onchange_callback) {
13308 			self.onChange.add(function(ed, l) {
13309 				ed.execCallback('onchange_callback', ed, l);
13310 			});
13311 		}
13312 	};
13313 
13314 	tinymce.Editor.prototype.bindNativeEvents = function() {
13315 		// 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
13316 		var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap;
13317 
13318 		nativeToDispatcherMap = {
13319 			mouseup : 'onMouseUp',
13320 			mousedown : 'onMouseDown',
13321 			click : 'onClick',
13322 			keyup : 'onKeyUp',
13323 			keydown : 'onKeyDown',
13324 			keypress : 'onKeyPress',
13325 			submit : 'onSubmit',
13326 			reset : 'onReset',
13327 			contextmenu : 'onContextMenu',
13328 			dblclick : 'onDblClick',
13329 			paste : 'onPaste' // Doesn't work in all browsers yet
13330 		};
13331 
13332 		// Handler that takes a native event and sends it out to a dispatcher like onKeyDown
13333 		function eventHandler(evt, args) {
13334 			var type = evt.type;
13335 
13336 			// Don't fire events when it's removed
13337 			if (self.removed)
13338 				return;
13339 
13340 			// Sends the native event out to a global dispatcher then to the specific event dispatcher
13341 			if (self.onEvent.dispatch(self, evt, args) !== false) {
13342 				self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args);
13343 			}
13344 		};
13345 
13346 		// Opera doesn't support focus event for contentEditable elements so we need to fake it
13347 		function doOperaFocus(e) {
13348 			self.focus(true);
13349 		};
13350 
13351 		function nodeChanged(ed, e) {
13352 			// Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
13353 			if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) {
13354 				self.selection.normalize();
13355 			}
13356 
13357 			self.nodeChanged();
13358 		}
13359 
13360 		// Add DOM events
13361 		each(nativeToDispatcherMap, function(dispatcherName, nativeName) {
13362 			var root = settings.content_editable ? self.getBody() : self.getDoc();
13363 
13364 			switch (nativeName) {
13365 				case 'contextmenu':
13366 					dom.bind(root, nativeName, eventHandler);
13367 					break;
13368 
13369 				case 'paste':
13370 					dom.bind(self.getBody(), nativeName, eventHandler);
13371 					break;
13372 
13373 				case 'submit':
13374 				case 'reset':
13375 					dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler);
13376 					break;
13377 
13378 				default:
13379 					dom.bind(root, nativeName, eventHandler);
13380 			}
13381 		});
13382 
13383 		// Set the editor as active when focused
13384 		dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) {
13385 			self.focus(true);
13386 		});
13387 
13388 		if (settings.content_editable && tinymce.isOpera) {
13389 			dom.bind(self.getBody(), 'click', doOperaFocus);
13390 			dom.bind(self.getBody(), 'keydown', doOperaFocus);
13391 		}
13392 
13393 		// Add node change handler
13394 		self.onMouseUp.add(nodeChanged);
13395 
13396 		self.onKeyUp.add(function(ed, e) {
13397 			var keyCode = e.keyCode;
13398 
13399 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey)
13400 				nodeChanged(ed, e);
13401 		});
13402 
13403 		// Add reset handler
13404 		self.onReset.add(function() {
13405 			self.setContent(self.startContent, {format : 'raw'});
13406 		});
13407 
13408 		// Add shortcuts
13409 		function handleShortcut(e, execute) {
13410 			if (e.altKey || e.ctrlKey || e.metaKey) {
13411 				each(self.shortcuts, function(shortcut) {
13412 					var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey;
13413 
13414 					if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey)
13415 						return;
13416 
13417 					if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
13418 						e.preventDefault();
13419 
13420 						if (execute) {
13421 							shortcut.func.call(shortcut.scope);
13422 						}
13423 
13424 						return true;
13425 					}
13426 				});
13427 			}
13428 		};
13429 
13430 		self.onKeyUp.add(function(ed, e) {
13431 			handleShortcut(e);
13432 		});
13433 
13434 		self.onKeyPress.add(function(ed, e) {
13435 			handleShortcut(e);
13436 		});
13437 
13438 		self.onKeyDown.add(function(ed, e) {
13439 			handleShortcut(e, true);
13440 		});
13441 
13442 		if (tinymce.isOpera) {
13443 			self.onClick.add(function(ed, e) {
13444 				e.preventDefault();
13445 			});
13446 		}
13447 	};
13448 })(tinymce);
13449 (function(tinymce) {
13450 	// Added for compression purposes
13451 	var each = tinymce.each, undef, TRUE = true, FALSE = false;
13452 
13453 	tinymce.EditorCommands = function(editor) {
13454 		var dom = editor.dom,
13455 			selection = editor.selection,
13456 			commands = {state: {}, exec : {}, value : {}},
13457 			settings = editor.settings,
13458 			formatter = editor.formatter,
13459 			bookmark;
13460 
13461 		function execCommand(command, ui, value) {
13462 			var func;
13463 
13464 			command = command.toLowerCase();
13465 			if (func = commands.exec[command]) {
13466 				func(command, ui, value);
13467 				return TRUE;
13468 			}
13469 
13470 			return FALSE;
13471 		};
13472 
13473 		function queryCommandState(command) {
13474 			var func;
13475 
13476 			command = command.toLowerCase();
13477 			if (func = commands.state[command])
13478 				return func(command);
13479 
13480 			return -1;
13481 		};
13482 
13483 		function queryCommandValue(command) {
13484 			var func;
13485 
13486 			command = command.toLowerCase();
13487 			if (func = commands.value[command])
13488 				return func(command);
13489 
13490 			return FALSE;
13491 		};
13492 
13493 		function addCommands(command_list, type) {
13494 			type = type || 'exec';
13495 
13496 			each(command_list, function(callback, command) {
13497 				each(command.toLowerCase().split(','), function(command) {
13498 					commands[type][command] = callback;
13499 				});
13500 			});
13501 		};
13502 
13503 		// Expose public methods
13504 		tinymce.extend(this, {
13505 			execCommand : execCommand,
13506 			queryCommandState : queryCommandState,
13507 			queryCommandValue : queryCommandValue,
13508 			addCommands : addCommands
13509 		});
13510 
13511 		// Private methods
13512 
13513 		function execNativeCommand(command, ui, value) {
13514 			if (ui === undef)
13515 				ui = FALSE;
13516 
13517 			if (value === undef)
13518 				value = null;
13519 
13520 			return editor.getDoc().execCommand(command, ui, value);
13521 		};
13522 
13523 		function isFormatMatch(name) {
13524 			return formatter.match(name);
13525 		};
13526 
13527 		function toggleFormat(name, value) {
13528 			formatter.toggle(name, value ? {value : value} : undef);
13529 		};
13530 
13531 		function storeSelection(type) {
13532 			bookmark = selection.getBookmark(type);
13533 		};
13534 
13535 		function restoreSelection() {
13536 			selection.moveToBookmark(bookmark);
13537 		};
13538 
13539 		// Add execCommand overrides
13540 		addCommands({
13541 			// Ignore these, added for compatibility
13542 			'mceResetDesignMode,mceBeginUndoLevel' : function() {},
13543 
13544 			// Add undo manager logic
13545 			'mceEndUndoLevel,mceAddUndoLevel' : function() {
13546 				editor.undoManager.add();
13547 			},
13548 
13549 			'Cut,Copy,Paste' : function(command) {
13550 				var doc = editor.getDoc(), failed;
13551 
13552 				// Try executing the native command
13553 				try {
13554 					execNativeCommand(command);
13555 				} catch (ex) {
13556 					// Command failed
13557 					failed = TRUE;
13558 				}
13559 
13560 				// Present alert message about clipboard access not being available
13561 				if (failed || !doc.queryCommandSupported(command)) {
13562 					if (tinymce.isGecko) {
13563 						editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
13564 							if (state)
13565 								open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
13566 						});
13567 					} else
13568 						editor.windowManager.alert(editor.getLang('clipboard_no_support'));
13569 				}
13570 			},
13571 
13572 			// Override unlink command
13573 			unlink : function(command) {
13574 				if (selection.isCollapsed())
13575 					selection.select(selection.getNode());
13576 
13577 				execNativeCommand(command);
13578 				selection.collapse(FALSE);
13579 			},
13580 
13581 			// Override justify commands to use the text formatter engine
13582 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
13583 				var align = command.substring(7);
13584 
13585 				// Remove all other alignments first
13586 				each('left,center,right,full'.split(','), function(name) {
13587 					if (align != name)
13588 						formatter.remove('align' + name);
13589 				});
13590 
13591 				toggleFormat('align' + align);
13592 				execCommand('mceRepaint');
13593 			},
13594 
13595 			// Override list commands to fix WebKit bug
13596 			'InsertUnorderedList,InsertOrderedList' : function(command) {
13597 				var listElm, listParent;
13598 
13599 				execNativeCommand(command);
13600 
13601 				// WebKit produces lists within block elements so we need to split them
13602 				// we will replace the native list creation logic to custom logic later on
13603 				// TODO: Remove this when the list creation logic is removed
13604 				listElm = dom.getParent(selection.getNode(), 'ol,ul');
13605 				if (listElm) {
13606 					listParent = listElm.parentNode;
13607 
13608 					// If list is within a text block then split that block
13609 					if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
13610 						storeSelection();
13611 						dom.split(listParent, listElm);
13612 						restoreSelection();
13613 					}
13614 				}
13615 			},
13616 
13617 			// Override commands to use the text formatter engine
13618 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
13619 				toggleFormat(command);
13620 			},
13621 
13622 			// Override commands to use the text formatter engine
13623 			'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
13624 				toggleFormat(command, value);
13625 			},
13626 
13627 			FontSize : function(command, ui, value) {
13628 				var fontClasses, fontSizes;
13629 
13630 				// Convert font size 1-7 to styles
13631 				if (value >= 1 && value <= 7) {
13632 					fontSizes = tinymce.explode(settings.font_size_style_values);
13633 					fontClasses = tinymce.explode(settings.font_size_classes);
13634 
13635 					if (fontClasses)
13636 						value = fontClasses[value - 1] || value;
13637 					else
13638 						value = fontSizes[value - 1] || value;
13639 				}
13640 
13641 				toggleFormat(command, value);
13642 			},
13643 
13644 			RemoveFormat : function(command) {
13645 				formatter.remove(command);
13646 			},
13647 
13648 			mceBlockQuote : function(command) {
13649 				toggleFormat('blockquote');
13650 			},
13651 
13652 			FormatBlock : function(command, ui, value) {
13653 				return toggleFormat(value || 'p');
13654 			},
13655 
13656 			mceCleanup : function() {
13657 				var bookmark = selection.getBookmark();
13658 
13659 				editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
13660 
13661 				selection.moveToBookmark(bookmark);
13662 			},
13663 
13664 			mceRemoveNode : function(command, ui, value) {
13665 				var node = value || selection.getNode();
13666 
13667 				// Make sure that the body node isn't removed
13668 				if (node != editor.getBody()) {
13669 					storeSelection();
13670 					editor.dom.remove(node, TRUE);
13671 					restoreSelection();
13672 				}
13673 			},
13674 
13675 			mceSelectNodeDepth : function(command, ui, value) {
13676 				var counter = 0;
13677 
13678 				dom.getParent(selection.getNode(), function(node) {
13679 					if (node.nodeType == 1 && counter++ == value) {
13680 						selection.select(node);
13681 						return FALSE;
13682 					}
13683 				}, editor.getBody());
13684 			},
13685 
13686 			mceSelectNode : function(command, ui, value) {
13687 				selection.select(value);
13688 			},
13689 
13690 			mceInsertContent : function(command, ui, value) {
13691 				var parser, serializer, parentNode, rootNode, fragment, args,
13692 					marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
13693 
13694 				//selection.normalize();
13695 
13696 				// Setup parser and serializer
13697 				parser = editor.parser;
13698 				serializer = new tinymce.html.Serializer({}, editor.schema);
13699 				bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
13700 
13701 				// Run beforeSetContent handlers on the HTML to be inserted
13702 				args = {content: value, format: 'html'};
13703 				selection.onBeforeSetContent.dispatch(selection, args);
13704 				value = args.content;
13705 
13706 				// Add caret at end of contents if it's missing
13707 				if (value.indexOf('{$caret}') == -1)
13708 					value += '{$caret}';
13709 
13710 				// Replace the caret marker with a span bookmark element
13711 				value = value.replace(/\{\$caret\}/, bookmarkHtml);
13712 
13713 				// Insert node maker where we will insert the new HTML and get it's parent
13714 				if (!selection.isCollapsed())
13715 					editor.getDoc().execCommand('Delete', false, null);
13716 
13717 				parentNode = selection.getNode();
13718 
13719 				// Parse the fragment within the context of the parent node
13720 				args = {context : parentNode.nodeName.toLowerCase()};
13721 				fragment = parser.parse(value, args);
13722 
13723 				// Move the caret to a more suitable location
13724 				node = fragment.lastChild;
13725 				if (node.attr('id') == 'mce_marker') {
13726 					marker = node;
13727 
13728 					for (node = node.prev; node; node = node.walk(true)) {
13729 						if (node.type == 3 || !dom.isBlock(node.name)) {
13730 							node.parent.insert(marker, node, node.name === 'br');
13731 							break;
13732 						}
13733 					}
13734 				}
13735 
13736 				// If parser says valid we can insert the contents into that parent
13737 				if (!args.invalid) {
13738 					value = serializer.serialize(fragment);
13739 
13740 					// Check if parent is empty or only has one BR element then set the innerHTML of that parent
13741 					node = parentNode.firstChild;
13742 					node2 = parentNode.lastChild;
13743 					if (!node || (node === node2 && node.nodeName === 'BR'))
13744 						dom.setHTML(parentNode, value);
13745 					else
13746 						selection.setContent(value);
13747 				} else {
13748 					// If the fragment was invalid within that context then we need
13749 					// to parse and process the parent it's inserted into
13750 
13751 					// Insert bookmark node and get the parent
13752 					selection.setContent(bookmarkHtml);
13753 					parentNode = editor.selection.getNode();
13754 					rootNode = editor.getBody();
13755 
13756 					// Opera will return the document node when selection is in root
13757 					if (parentNode.nodeType == 9)
13758 						parentNode = node = rootNode;
13759 					else
13760 						node = parentNode;
13761 
13762 					// Find the ancestor just before the root element
13763 					while (node !== rootNode) {
13764 						parentNode = node;
13765 						node = node.parentNode;
13766 					}
13767 
13768 					// Get the outer/inner HTML depending on if we are in the root and parser and serialize that
13769 					value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
13770 					value = serializer.serialize(
13771 						parser.parse(
13772 							// Need to replace by using a function since $ in the contents would otherwise be a problem
13773 							value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
13774 								return serializer.serialize(fragment);
13775 							})
13776 						)
13777 					);
13778 
13779 					// Set the inner/outer HTML depending on if we are in the root or not
13780 					if (parentNode == rootNode)
13781 						dom.setHTML(rootNode, value);
13782 					else
13783 						dom.setOuterHTML(parentNode, value);
13784 				}
13785 
13786 				marker = dom.get('mce_marker');
13787 
13788 				// Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
13789 				nodeRect = dom.getRect(marker);
13790 				viewPortRect = dom.getViewPort(editor.getWin());
13791 
13792 				// Check if node is out side the viewport if it is then scroll to it
13793 				if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
13794 					(nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
13795 					viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
13796 					viewportBodyElement.scrollLeft = nodeRect.x;
13797 					viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
13798 				}
13799 
13800 				// Move selection before marker and remove it
13801 				rng = dom.createRng();
13802 
13803 				// If previous sibling is a text node set the selection to the end of that node
13804 				node = marker.previousSibling;
13805 				if (node && node.nodeType == 3) {
13806 					rng.setStart(node, node.nodeValue.length);
13807 				} else {
13808 					// If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
13809 					rng.setStartBefore(marker);
13810 					rng.setEndBefore(marker);
13811 				}
13812 
13813 				// Remove the marker node and set the new range
13814 				dom.remove(marker);
13815 				selection.setRng(rng);
13816 
13817 				// Dispatch after event and add any visual elements needed
13818 				selection.onSetContent.dispatch(selection, args);
13819 				editor.addVisual();
13820 			},
13821 
13822 			mceInsertRawHTML : function(command, ui, value) {
13823 				selection.setContent('tiny_mce_marker');
13824 				editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
13825 			},
13826 
13827 			mceToggleFormat : function(command, ui, value) {
13828 				toggleFormat(value);
13829 			},
13830 
13831 			mceSetContent : function(command, ui, value) {
13832 				editor.setContent(value);
13833 			},
13834 
13835 			'Indent,Outdent' : function(command) {
13836 				var intentValue, indentUnit, value;
13837 
13838 				// Setup indent level
13839 				intentValue = settings.indentation;
13840 				indentUnit = /[a-z%]+$/i.exec(intentValue);
13841 				intentValue = parseInt(intentValue);
13842 
13843 				if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
13844 					// If forced_root_blocks is set to false we don't have a block to indent so lets create a div
13845 					if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
13846 						formatter.apply('div');
13847 					}
13848 
13849 					each(selection.getSelectedBlocks(), function(element) {
13850 						if (command == 'outdent') {
13851 							value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
13852 							dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
13853 						} else
13854 							dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
13855 					});
13856 				} else
13857 					execNativeCommand(command);
13858 			},
13859 
13860 			mceRepaint : function() {
13861 				var bookmark;
13862 
13863 				if (tinymce.isGecko) {
13864 					try {
13865 						storeSelection(TRUE);
13866 
13867 						if (selection.getSel())
13868 							selection.getSel().selectAllChildren(editor.getBody());
13869 
13870 						selection.collapse(TRUE);
13871 						restoreSelection();
13872 					} catch (ex) {
13873 						// Ignore
13874 					}
13875 				}
13876 			},
13877 
13878 			mceToggleFormat : function(command, ui, value) {
13879 				formatter.toggle(value);
13880 			},
13881 
13882 			InsertHorizontalRule : function() {
13883 				editor.execCommand('mceInsertContent', false, '<hr />');
13884 			},
13885 
13886 			mceToggleVisualAid : function() {
13887 				editor.hasVisual = !editor.hasVisual;
13888 				editor.addVisual();
13889 			},
13890 
13891 			mceReplaceContent : function(command, ui, value) {
13892 				editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
13893 			},
13894 
13895 			mceInsertLink : function(command, ui, value) {
13896 				var anchor;
13897 
13898 				if (typeof(value) == 'string')
13899 					value = {href : value};
13900 
13901 				anchor = dom.getParent(selection.getNode(), 'a');
13902 
13903 				// Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
13904 				value.href = value.href.replace(' ', '%20');
13905 
13906 				// Remove existing links if there could be child links or that the href isn't specified
13907 				if (!anchor || !value.href) {
13908 					formatter.remove('link');
13909 				}		
13910 
13911 				// Apply new link to selection
13912 				if (value.href) {
13913 					formatter.apply('link', value, anchor);
13914 				}
13915 			},
13916 
13917 			selectAll : function() {
13918 				var root = dom.getRoot(), rng = dom.createRng();
13919 
13920 				rng.setStart(root, 0);
13921 				rng.setEnd(root, root.childNodes.length);
13922 
13923 				editor.selection.setRng(rng);
13924 			}
13925 		});
13926 
13927 		// Add queryCommandState overrides
13928 		addCommands({
13929 			// Override justify commands
13930 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
13931 				var name = 'align' + command.substring(7);
13932 				var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
13933 				var matches = tinymce.map(nodes, function(node) {
13934 					return !!formatter.matchNode(node, name);
13935 				});
13936 				return tinymce.inArray(matches, TRUE) !== -1;
13937 			},
13938 
13939 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
13940 				return isFormatMatch(command);
13941 			},
13942 
13943 			mceBlockQuote : function() {
13944 				return isFormatMatch('blockquote');
13945 			},
13946 
13947 			Outdent : function() {
13948 				var node;
13949 
13950 				if (settings.inline_styles) {
13951 					if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
13952 						return TRUE;
13953 
13954 					if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
13955 						return TRUE;
13956 				}
13957 
13958 				return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
13959 			},
13960 
13961 			'InsertUnorderedList,InsertOrderedList' : function(command) {
13962 				return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
13963 			}
13964 		}, 'state');
13965 
13966 		// Add queryCommandValue overrides
13967 		addCommands({
13968 			'FontSize,FontName' : function(command) {
13969 				var value = 0, parent;
13970 
13971 				if (parent = dom.getParent(selection.getNode(), 'span')) {
13972 					if (command == 'fontsize')
13973 						value = parent.style.fontSize;
13974 					else
13975 						value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
13976 				}
13977 
13978 				return value;
13979 			}
13980 		}, 'value');
13981 
13982 		// Add undo manager logic
13983 		addCommands({
13984 			Undo : function() {
13985 				editor.undoManager.undo();
13986 			},
13987 
13988 			Redo : function() {
13989 				editor.undoManager.redo();
13990 			}
13991 		});
13992 	};
13993 })(tinymce);
13994 
13995 (function(tinymce) {
13996 	var Dispatcher = tinymce.util.Dispatcher;
13997 
13998 	tinymce.UndoManager = function(editor) {
13999 		var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo;
14000 
14001 		function getContent() {
14002 			// Remove whitespace before/after and remove pure bogus nodes
14003 			return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, ''));
14004 		};
14005 
14006 		function addNonTypingUndoLevel() {
14007 			self.typing = false;
14008 			self.add();
14009 		};
14010 
14011 		// Create event instances
14012 		onBeforeAdd = new Dispatcher(self);
14013 		onAdd       = new Dispatcher(self);
14014 		onUndo      = new Dispatcher(self);
14015 		onRedo      = new Dispatcher(self);
14016 
14017 		// Pass though onAdd event from UndoManager to Editor as onChange
14018 		onAdd.add(function(undoman, level) {
14019 			if (undoman.hasUndo())
14020 				return editor.onChange.dispatch(editor, level, undoman);
14021 		});
14022 
14023 		// Pass though onUndo event from UndoManager to Editor
14024 		onUndo.add(function(undoman, level) {
14025 			return editor.onUndo.dispatch(editor, level, undoman);
14026 		});
14027 
14028 		// Pass though onRedo event from UndoManager to Editor
14029 		onRedo.add(function(undoman, level) {
14030 			return editor.onRedo.dispatch(editor, level, undoman);
14031 		});
14032 
14033 		// Add initial undo level when the editor is initialized
14034 		editor.onInit.add(function() {
14035 			self.add();
14036 		});
14037 
14038 		// Get position before an execCommand is processed
14039 		editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) {
14040 			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
14041 				self.beforeChange();
14042 			}
14043 		});
14044 
14045 		// Add undo level after an execCommand call was made
14046 		editor.onExecCommand.add(function(ed, cmd, ui, val, args) {
14047 			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
14048 				self.add();
14049 			}
14050 		});
14051 
14052 		// Add undo level on save contents, drag end and blur/focusout
14053 		editor.onSaveContent.add(addNonTypingUndoLevel);
14054 		editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
14055 		editor.dom.bind(editor.getDoc(), tinymce.isGecko ? 'blur' : 'focusout', function(e) {
14056 			if (!editor.removed && self.typing) {
14057 				addNonTypingUndoLevel();
14058 			}
14059 		});
14060 
14061 		editor.onKeyUp.add(function(editor, e) {
14062 			var keyCode = e.keyCode;
14063 
14064 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
14065 				addNonTypingUndoLevel();
14066 			}
14067 		});
14068 
14069 		editor.onKeyDown.add(function(editor, e) {
14070 			var keyCode = e.keyCode;
14071 
14072 			// Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
14073 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
14074 				if (self.typing) {
14075 					addNonTypingUndoLevel();
14076 				}
14077 
14078 				return;
14079 			}
14080 
14081 			// If key isn't shift,ctrl,alt,capslock,metakey
14082 			if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {
14083 				self.beforeChange();
14084 				self.typing = true;
14085 				self.add();
14086 			}
14087 		});
14088 
14089 		editor.onMouseDown.add(function(editor, e) {
14090 			if (self.typing) {
14091 				addNonTypingUndoLevel();
14092 			}
14093 		});
14094 
14095 		// Add keyboard shortcuts for undo/redo keys
14096 		editor.addShortcut('ctrl+z', 'undo_desc', 'Undo');
14097 		editor.addShortcut('ctrl+y', 'redo_desc', 'Redo');
14098 
14099 		self = {
14100 			// Explose for debugging reasons
14101 			data : data,
14102 
14103 			typing : false,
14104 			
14105 			onBeforeAdd: onBeforeAdd,
14106 
14107 			onAdd : onAdd,
14108 
14109 			onUndo : onUndo,
14110 
14111 			onRedo : onRedo,
14112 
14113 			beforeChange : function() {
14114 				beforeBookmark = editor.selection.getBookmark(2, true);
14115 			},
14116 
14117 			add : function(level) {
14118 				var i, settings = editor.settings, lastLevel;
14119 
14120 				level = level || {};
14121 				level.content = getContent();
14122 				
14123 				self.onBeforeAdd.dispatch(self, level);
14124 
14125 				// Add undo level if needed
14126 				lastLevel = data[index];
14127 				if (lastLevel && lastLevel.content == level.content)
14128 					return null;
14129 
14130 				// Set before bookmark on previous level
14131 				if (data[index])
14132 					data[index].beforeBookmark = beforeBookmark;
14133 
14134 				// Time to compress
14135 				if (settings.custom_undo_redo_levels) {
14136 					if (data.length > settings.custom_undo_redo_levels) {
14137 						for (i = 0; i < data.length - 1; i++)
14138 							data[i] = data[i + 1];
14139 
14140 						data.length--;
14141 						index = data.length;
14142 					}
14143 				}
14144 
14145 				// Get a non intrusive normalized bookmark
14146 				level.bookmark = editor.selection.getBookmark(2, true);
14147 
14148 				// Crop array if needed
14149 				if (index < data.length - 1)
14150 					data.length = index + 1;
14151 
14152 				data.push(level);
14153 				index = data.length - 1;
14154 
14155 				self.onAdd.dispatch(self, level);
14156 				editor.isNotDirty = 0;
14157 
14158 				return level;
14159 			},
14160 
14161 			undo : function() {
14162 				var level, i;
14163 
14164 				if (self.typing) {
14165 					self.add();
14166 					self.typing = false;
14167 				}
14168 
14169 				if (index > 0) {
14170 					level = data[--index];
14171 
14172 					editor.setContent(level.content, {format : 'raw'});
14173 					editor.selection.moveToBookmark(level.beforeBookmark);
14174 
14175 					self.onUndo.dispatch(self, level);
14176 				}
14177 
14178 				return level;
14179 			},
14180 
14181 			redo : function() {
14182 				var level;
14183 
14184 				if (index < data.length - 1) {
14185 					level = data[++index];
14186 
14187 					editor.setContent(level.content, {format : 'raw'});
14188 					editor.selection.moveToBookmark(level.bookmark);
14189 
14190 					self.onRedo.dispatch(self, level);
14191 				}
14192 
14193 				return level;
14194 			},
14195 
14196 			clear : function() {
14197 				data = [];
14198 				index = 0;
14199 				self.typing = false;
14200 			},
14201 
14202 			hasUndo : function() {
14203 				return index > 0 || this.typing;
14204 			},
14205 
14206 			hasRedo : function() {
14207 				return index < data.length - 1 && !this.typing;
14208 			}
14209 		};
14210 
14211 		return self;
14212 	};
14213 })(tinymce);
14214 
14215 tinymce.ForceBlocks = function(editor) {
14216 	var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements();
14217 
14218 	function addRootBlocks() {
14219 		var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument;
14220 
14221 		if (!node || node.nodeType !== 1 || !settings.forced_root_block)
14222 			return;
14223 
14224 		// Check if node is wrapped in block
14225 		while (node && node != rootNode) {
14226 			if (blockElements[node.nodeName])
14227 				return;
14228 
14229 			node = node.parentNode;
14230 		}
14231 
14232 		// Get current selection
14233 		rng = selection.getRng();
14234 		if (rng.setStart) {
14235 			startContainer = rng.startContainer;
14236 			startOffset = rng.startOffset;
14237 			endContainer = rng.endContainer;
14238 			endOffset = rng.endOffset;
14239 		} else {
14240 			// Force control range into text range
14241 			if (rng.item) {
14242 				node = rng.item(0);
14243 				rng = editor.getDoc().body.createTextRange();
14244 				rng.moveToElementText(node);
14245 			}
14246 
14247 			isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc();
14248 			tmpRng = rng.duplicate();
14249 			tmpRng.collapse(true);
14250 			startOffset = tmpRng.move('character', offset) * -1;
14251 
14252 			if (!tmpRng.collapsed) {
14253 				tmpRng = rng.duplicate();
14254 				tmpRng.collapse(false);
14255 				endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
14256 			}
14257 		}
14258 
14259 		// Wrap non block elements and text nodes
14260 		node = rootNode.firstChild;
14261 		while (node) {
14262 			if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
14263 				if (!rootBlockNode) {
14264 					rootBlockNode = dom.create(settings.forced_root_block);
14265 					node.parentNode.insertBefore(rootBlockNode, node);
14266 					wrapped = true;
14267 				}
14268 
14269 				tempNode = node;
14270 				node = node.nextSibling;
14271 				rootBlockNode.appendChild(tempNode);
14272 			} else {
14273 				rootBlockNode = null;
14274 				node = node.nextSibling;
14275 			}
14276 		}
14277 
14278 		if (wrapped) {
14279 			if (rng.setStart) {
14280 				rng.setStart(startContainer, startOffset);
14281 				rng.setEnd(endContainer, endOffset);
14282 				selection.setRng(rng);
14283 			} else {
14284 				// Only select if the previous selection was inside the document to prevent auto focus in quirks mode
14285 				if (isInEditorDocument) {
14286 					try {
14287 						rng = editor.getDoc().body.createTextRange();
14288 						rng.moveToElementText(rootNode);
14289 						rng.collapse(true);
14290 						rng.moveStart('character', startOffset);
14291 
14292 						if (endOffset > 0)
14293 							rng.moveEnd('character', endOffset);
14294 
14295 						rng.select();
14296 					} catch (ex) {
14297 						// Ignore
14298 					}
14299 				}
14300 			}
14301 
14302 			editor.nodeChanged();
14303 		}
14304 	};
14305 
14306 	// Force root blocks
14307 	if (settings.forced_root_block) {
14308 		editor.onKeyUp.add(addRootBlocks);
14309 		editor.onNodeChange.add(addRootBlocks);
14310 	}
14311 };
14312 
14313 (function(tinymce) {
14314 	// Shorten names
14315 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
14316 
14317 	tinymce.create('tinymce.ControlManager', {
14318 		ControlManager : function(ed, s) {
14319 			var t = this, i;
14320 
14321 			s = s || {};
14322 			t.editor = ed;
14323 			t.controls = {};
14324 			t.onAdd = new tinymce.util.Dispatcher(t);
14325 			t.onPostRender = new tinymce.util.Dispatcher(t);
14326 			t.prefix = s.prefix || ed.id + '_';
14327 			t._cls = {};
14328 
14329 			t.onPostRender.add(function() {
14330 				each(t.controls, function(c) {
14331 					c.postRender();
14332 				});
14333 			});
14334 		},
14335 
14336 		get : function(id) {
14337 			return this.controls[this.prefix + id] || this.controls[id];
14338 		},
14339 
14340 		setActive : function(id, s) {
14341 			var c = null;
14342 
14343 			if (c = this.get(id))
14344 				c.setActive(s);
14345 
14346 			return c;
14347 		},
14348 
14349 		setDisabled : function(id, s) {
14350 			var c = null;
14351 
14352 			if (c = this.get(id))
14353 				c.setDisabled(s);
14354 
14355 			return c;
14356 		},
14357 
14358 		add : function(c) {
14359 			var t = this;
14360 
14361 			if (c) {
14362 				t.controls[c.id] = c;
14363 				t.onAdd.dispatch(c, t);
14364 			}
14365 
14366 			return c;
14367 		},
14368 
14369 		createControl : function(name) {
14370 			var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName;
14371 
14372 			// Build control factory cache
14373 			if (!self.controlFactories) {
14374 				self.controlFactories = [];
14375 				each(editor.plugins, function(plugin) {
14376 					if (plugin.createControl) {
14377 						self.controlFactories.push(plugin);
14378 					}
14379 				});
14380 			}
14381 
14382 			// Create controls by asking cached factories
14383 			factories = self.controlFactories;
14384 			for (i = 0, l = factories.length; i < l; i++) {
14385 				ctrl = factories[i].createControl(name, self);
14386 
14387 				if (ctrl) {
14388 					return self.add(ctrl);
14389 				}
14390 			}
14391 
14392 			// Create sepearator
14393 			if (name === "|" || name === "separator") {
14394 				return self.createSeparator();
14395 			}
14396 
14397 			// Create control from button collection
14398 			if (editor.buttons && (ctrl = editor.buttons[name])) {
14399 				return self.createButton(name, ctrl);
14400 			}
14401 
14402 			return self.add(ctrl);
14403 		},
14404 
14405 		createDropMenu : function(id, s, cc) {
14406 			var t = this, ed = t.editor, c, bm, v, cls;
14407 
14408 			s = extend({
14409 				'class' : 'mceDropDown',
14410 				constrain : ed.settings.constrain_menus
14411 			}, s);
14412 
14413 			s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
14414 			if (v = ed.getParam('skin_variant'))
14415 				s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
14416 
14417 			s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : '';
14418 
14419 			id = t.prefix + id;
14420 			cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
14421 			c = t.controls[id] = new cls(id, s);
14422 			c.onAddItem.add(function(c, o) {
14423 				var s = o.settings;
14424 
14425 				s.title = ed.getLang(s.title, s.title);
14426 
14427 				if (!s.onclick) {
14428 					s.onclick = function(v) {
14429 						if (s.cmd)
14430 							ed.execCommand(s.cmd, s.ui || false, s.value);
14431 					};
14432 				}
14433 			});
14434 
14435 			ed.onRemove.add(function() {
14436 				c.destroy();
14437 			});
14438 
14439 			// Fix for bug #1897785, #1898007
14440 			if (tinymce.isIE) {
14441 				c.onShowMenu.add(function() {
14442 					// IE 8 needs focus in order to store away a range with the current collapsed caret location
14443 					ed.focus();
14444 
14445 					bm = ed.selection.getBookmark(1);
14446 				});
14447 
14448 				c.onHideMenu.add(function() {
14449 					if (bm) {
14450 						ed.selection.moveToBookmark(bm);
14451 						bm = 0;
14452 					}
14453 				});
14454 			}
14455 
14456 			return t.add(c);
14457 		},
14458 
14459 		createListBox : function(id, s, cc) {
14460 			var t = this, ed = t.editor, cmd, c, cls;
14461 
14462 			if (t.get(id))
14463 				return null;
14464 
14465 			s.title = ed.translate(s.title);
14466 			s.scope = s.scope || ed;
14467 
14468 			if (!s.onselect) {
14469 				s.onselect = function(v) {
14470 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
14471 				};
14472 			}
14473 
14474 			s = extend({
14475 				title : s.title,
14476 				'class' : 'mce_' + id,
14477 				scope : s.scope,
14478 				control_manager : t
14479 			}, s);
14480 
14481 			id = t.prefix + id;
14482 
14483 
14484 			function useNativeListForAccessibility(ed) {
14485 				return ed.settings.use_accessible_selects && !tinymce.isGecko
14486 			}
14487 
14488 			if (ed.settings.use_native_selects || useNativeListForAccessibility(ed))
14489 				c = new tinymce.ui.NativeListBox(id, s);
14490 			else {
14491 				cls = cc || t._cls.listbox || tinymce.ui.ListBox;
14492 				c = new cls(id, s, ed);
14493 			}
14494 
14495 			t.controls[id] = c;
14496 
14497 			// Fix focus problem in Safari
14498 			if (tinymce.isWebKit) {
14499 				c.onPostRender.add(function(c, n) {
14500 					// Store bookmark on mousedown
14501 					Event.add(n, 'mousedown', function() {
14502 						ed.bookmark = ed.selection.getBookmark(1);
14503 					});
14504 
14505 					// Restore on focus, since it might be lost
14506 					Event.add(n, 'focus', function() {
14507 						ed.selection.moveToBookmark(ed.bookmark);
14508 						ed.bookmark = null;
14509 					});
14510 				});
14511 			}
14512 
14513 			if (c.hideMenu)
14514 				ed.onMouseDown.add(c.hideMenu, c);
14515 
14516 			return t.add(c);
14517 		},
14518 
14519 		createButton : function(id, s, cc) {
14520 			var t = this, ed = t.editor, o, c, cls;
14521 
14522 			if (t.get(id))
14523 				return null;
14524 
14525 			s.title = ed.translate(s.title);
14526 			s.label = ed.translate(s.label);
14527 			s.scope = s.scope || ed;
14528 
14529 			if (!s.onclick && !s.menu_button) {
14530 				s.onclick = function() {
14531 					ed.execCommand(s.cmd, s.ui || false, s.value);
14532 				};
14533 			}
14534 
14535 			s = extend({
14536 				title : s.title,
14537 				'class' : 'mce_' + id,
14538 				unavailable_prefix : ed.getLang('unavailable', ''),
14539 				scope : s.scope,
14540 				control_manager : t
14541 			}, s);
14542 
14543 			id = t.prefix + id;
14544 
14545 			if (s.menu_button) {
14546 				cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
14547 				c = new cls(id, s, ed);
14548 				ed.onMouseDown.add(c.hideMenu, c);
14549 			} else {
14550 				cls = t._cls.button || tinymce.ui.Button;
14551 				c = new cls(id, s, ed);
14552 			}
14553 
14554 			return t.add(c);
14555 		},
14556 
14557 		createMenuButton : function(id, s, cc) {
14558 			s = s || {};
14559 			s.menu_button = 1;
14560 
14561 			return this.createButton(id, s, cc);
14562 		},
14563 
14564 		createSplitButton : function(id, s, cc) {
14565 			var t = this, ed = t.editor, cmd, c, cls;
14566 
14567 			if (t.get(id))
14568 				return null;
14569 
14570 			s.title = ed.translate(s.title);
14571 			s.scope = s.scope || ed;
14572 
14573 			if (!s.onclick) {
14574 				s.onclick = function(v) {
14575 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
14576 				};
14577 			}
14578 
14579 			if (!s.onselect) {
14580 				s.onselect = function(v) {
14581 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
14582 				};
14583 			}
14584 
14585 			s = extend({
14586 				title : s.title,
14587 				'class' : 'mce_' + id,
14588 				scope : s.scope,
14589 				control_manager : t
14590 			}, s);
14591 
14592 			id = t.prefix + id;
14593 			cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
14594 			c = t.add(new cls(id, s, ed));
14595 			ed.onMouseDown.add(c.hideMenu, c);
14596 
14597 			return c;
14598 		},
14599 
14600 		createColorSplitButton : function(id, s, cc) {
14601 			var t = this, ed = t.editor, cmd, c, cls, bm;
14602 
14603 			if (t.get(id))
14604 				return null;
14605 
14606 			s.title = ed.translate(s.title);
14607 			s.scope = s.scope || ed;
14608 
14609 			if (!s.onclick) {
14610 				s.onclick = function(v) {
14611 					if (tinymce.isIE)
14612 						bm = ed.selection.getBookmark(1);
14613 
14614 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
14615 				};
14616 			}
14617 
14618 			if (!s.onselect) {
14619 				s.onselect = function(v) {
14620 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
14621 				};
14622 			}
14623 
14624 			s = extend({
14625 				title : s.title,
14626 				'class' : 'mce_' + id,
14627 				'menu_class' : ed.getParam('skin') + 'Skin',
14628 				scope : s.scope,
14629 				more_colors_title : ed.getLang('more_colors')
14630 			}, s);
14631 
14632 			id = t.prefix + id;
14633 			cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
14634 			c = new cls(id, s, ed);
14635 			ed.onMouseDown.add(c.hideMenu, c);
14636 
14637 			// Remove the menu element when the editor is removed
14638 			ed.onRemove.add(function() {
14639 				c.destroy();
14640 			});
14641 
14642 			// Fix for bug #1897785, #1898007
14643 			if (tinymce.isIE) {
14644 				c.onShowMenu.add(function() {
14645 					// IE 8 needs focus in order to store away a range with the current collapsed caret location
14646 					ed.focus();
14647 					bm = ed.selection.getBookmark(1);
14648 				});
14649 
14650 				c.onHideMenu.add(function() {
14651 					if (bm) {
14652 						ed.selection.moveToBookmark(bm);
14653 						bm = 0;
14654 					}
14655 				});
14656 			}
14657 
14658 			return t.add(c);
14659 		},
14660 
14661 		createToolbar : function(id, s, cc) {
14662 			var c, t = this, cls;
14663 
14664 			id = t.prefix + id;
14665 			cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
14666 			c = new cls(id, s, t.editor);
14667 
14668 			if (t.get(id))
14669 				return null;
14670 
14671 			return t.add(c);
14672 		},
14673 		
14674 		createToolbarGroup : function(id, s, cc) {
14675 			var c, t = this, cls;
14676 			id = t.prefix + id;
14677 			cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
14678 			c = new cls(id, s, t.editor);
14679 			
14680 			if (t.get(id))
14681 				return null;
14682 			
14683 			return t.add(c);
14684 		},
14685 
14686 		createSeparator : function(cc) {
14687 			var cls = cc || this._cls.separator || tinymce.ui.Separator;
14688 
14689 			return new cls();
14690 		},
14691 
14692 		setControlType : function(n, c) {
14693 			return this._cls[n.toLowerCase()] = c;
14694 		},
14695 	
14696 		destroy : function() {
14697 			each(this.controls, function(c) {
14698 				c.destroy();
14699 			});
14700 
14701 			this.controls = null;
14702 		}
14703 	});
14704 })(tinymce);
14705 
14706 (function(tinymce) {
14707 	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
14708 
14709 	tinymce.create('tinymce.WindowManager', {
14710 		WindowManager : function(ed) {
14711 			var t = this;
14712 
14713 			t.editor = ed;
14714 			t.onOpen = new Dispatcher(t);
14715 			t.onClose = new Dispatcher(t);
14716 			t.params = {};
14717 			t.features = {};
14718 		},
14719 
14720 		open : function(s, p) {
14721 			var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
14722 
14723 			// Default some options
14724 			s = s || {};
14725 			p = p || {};
14726 			sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
14727 			sh = isOpera ? vp.h : screen.height;
14728 			s.name = s.name || 'mc_' + new Date().getTime();
14729 			s.width = parseInt(s.width || 320);
14730 			s.height = parseInt(s.height || 240);
14731 			s.resizable = true;
14732 			s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
14733 			s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
14734 			p.inline = false;
14735 			p.mce_width = s.width;
14736 			p.mce_height = s.height;
14737 			p.mce_auto_focus = s.auto_focus;
14738 
14739 			if (mo) {
14740 				if (isIE) {
14741 					s.center = true;
14742 					s.help = false;
14743 					s.dialogWidth = s.width + 'px';
14744 					s.dialogHeight = s.height + 'px';
14745 					s.scroll = s.scrollbars || false;
14746 				}
14747 			}
14748 
14749 			// Build features string
14750 			each(s, function(v, k) {
14751 				if (tinymce.is(v, 'boolean'))
14752 					v = v ? 'yes' : 'no';
14753 
14754 				if (!/^(name|url)$/.test(k)) {
14755 					if (isIE && mo)
14756 						f += (f ? ';' : '') + k + ':' + v;
14757 					else
14758 						f += (f ? ',' : '') + k + '=' + v;
14759 				}
14760 			});
14761 
14762 			t.features = s;
14763 			t.params = p;
14764 			t.onOpen.dispatch(t, s, p);
14765 
14766 			u = s.url || s.file;
14767 			u = tinymce._addVer(u);
14768 
14769 			try {
14770 				if (isIE && mo) {
14771 					w = 1;
14772 					window.showModalDialog(u, window, f);
14773 				} else
14774 					w = window.open(u, s.name, f);
14775 			} catch (ex) {
14776 				// Ignore
14777 			}
14778 
14779 			if (!w)
14780 				alert(t.editor.getLang('popup_blocked'));
14781 		},
14782 
14783 		close : function(w) {
14784 			w.close();
14785 			this.onClose.dispatch(this);
14786 		},
14787 
14788 		createInstance : function(cl, a, b, c, d, e) {
14789 			var f = tinymce.resolve(cl);
14790 
14791 			return new f(a, b, c, d, e);
14792 		},
14793 
14794 		confirm : function(t, cb, s, w) {
14795 			w = w || window;
14796 
14797 			cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
14798 		},
14799 
14800 		alert : function(tx, cb, s, w) {
14801 			var t = this;
14802 
14803 			w = w || window;
14804 			w.alert(t._decode(t.editor.getLang(tx, tx)));
14805 
14806 			if (cb)
14807 				cb.call(s || t);
14808 		},
14809 
14810 		resizeBy : function(dw, dh, win) {
14811 			win.resizeBy(dw, dh);
14812 		},
14813 
14814 		// Internal functions
14815 
14816 		_decode : function(s) {
14817 			return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
14818 		}
14819 	});
14820 }(tinymce));
14821 (function(tinymce) {
14822 	tinymce.Formatter = function(ed) {
14823 		var formats = {},
14824 			each = tinymce.each,
14825 			dom = ed.dom,
14826 			selection = ed.selection,
14827 			TreeWalker = tinymce.dom.TreeWalker,
14828 			rangeUtils = new tinymce.dom.RangeUtils(dom),
14829 			isValid = ed.schema.isValidChild,
14830 			isBlock = dom.isBlock,
14831 			forcedRootBlock = ed.settings.forced_root_block,
14832 			nodeIndex = dom.nodeIndex,
14833 			INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF',
14834 			MCE_ATTR_RE = /^(src|href|style)$/,
14835 			FALSE = false,
14836 			TRUE = true,
14837 			formatChangeData,
14838 			undef,
14839 			getContentEditable = dom.getContentEditable;
14840 
14841 		function isArray(obj) {
14842 			return obj instanceof Array;
14843 		};
14844 
14845 		function getParents(node, selector) {
14846 			return dom.getParents(node, selector, dom.getRoot());
14847 		};
14848 
14849 		function isCaretNode(node) {
14850 			return node.nodeType === 1 && node.id === '_mce_caret';
14851 		};
14852 
14853 		function defaultFormats() {
14854 			register({
14855 				alignleft : [
14856 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'},
14857 					{selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
14858 				],
14859 
14860 				aligncenter : [
14861 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'},
14862 					{selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
14863 					{selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
14864 				],
14865 
14866 				alignright : [
14867 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'},
14868 					{selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
14869 				],
14870 
14871 				alignfull : [
14872 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'}
14873 				],
14874 
14875 				bold : [
14876 					{inline : 'strong', remove : 'all'},
14877 					{inline : 'span', styles : {fontWeight : 'bold'}},
14878 					{inline : 'b', remove : 'all'}
14879 				],
14880 
14881 				italic : [
14882 					{inline : 'em', remove : 'all'},
14883 					{inline : 'span', styles : {fontStyle : 'italic'}},
14884 					{inline : 'i', remove : 'all'}
14885 				],
14886 
14887 				underline : [
14888 					{inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
14889 					{inline : 'u', remove : 'all'}
14890 				],
14891 
14892 				strikethrough : [
14893 					{inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
14894 					{inline : 'strike', remove : 'all'}
14895 				],
14896 
14897 				forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
14898 				hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
14899 				fontname : {inline : 'span', styles : {fontFamily : '%value'}},
14900 				fontsize : {inline : 'span', styles : {fontSize : '%value'}},
14901 				fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
14902 				blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
14903 				subscript : {inline : 'sub'},
14904 				superscript : {inline : 'sup'},
14905 
14906 				link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,
14907 					onmatch : function(node) {
14908 						return true;
14909 					},
14910 
14911 					onformat : function(elm, fmt, vars) {
14912 						each(vars, function(value, key) {
14913 							dom.setAttrib(elm, key, value);
14914 						});
14915 					}
14916 				},
14917 
14918 				removeformat : [
14919 					{selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
14920 					{selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
14921 					{selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
14922 				]
14923 			});
14924 
14925 			// Register default block formats
14926 			each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
14927 				register(name, {block : name, remove : 'all'});
14928 			});
14929 
14930 			// Register user defined formats
14931 			register(ed.settings.formats);
14932 		};
14933 
14934 		function addKeyboardShortcuts() {
14935 			// Add some inline shortcuts
14936 			ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');
14937 			ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');
14938 			ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');
14939 
14940 			// BlockFormat shortcuts keys
14941 			for (var i = 1; i <= 6; i++) {
14942 				ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
14943 			}
14944 
14945 			ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
14946 			ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
14947 			ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
14948 		};
14949 
14950 		// Public functions
14951 
14952 		function get(name) {
14953 			return name ? formats[name] : formats;
14954 		};
14955 
14956 		function register(name, format) {
14957 			if (name) {
14958 				if (typeof(name) !== 'string') {
14959 					each(name, function(format, name) {
14960 						register(name, format);
14961 					});
14962 				} else {
14963 					// Force format into array and add it to internal collection
14964 					format = format.length ? format : [format];
14965 
14966 					each(format, function(format) {
14967 						// Set deep to false by default on selector formats this to avoid removing
14968 						// alignment on images inside paragraphs when alignment is changed on paragraphs
14969 						if (format.deep === undef)
14970 							format.deep = !format.selector;
14971 
14972 						// Default to true
14973 						if (format.split === undef)
14974 							format.split = !format.selector || format.inline;
14975 
14976 						// Default to true
14977 						if (format.remove === undef && format.selector && !format.inline)
14978 							format.remove = 'none';
14979 
14980 						// Mark format as a mixed format inline + block level
14981 						if (format.selector && format.inline) {
14982 							format.mixed = true;
14983 							format.block_expand = true;
14984 						}
14985 
14986 						// Split classes if needed
14987 						if (typeof(format.classes) === 'string')
14988 							format.classes = format.classes.split(/\s+/);
14989 					});
14990 
14991 					formats[name] = format;
14992 				}
14993 			}
14994 		};
14995 
14996 		var getTextDecoration = function(node) {
14997 			var decoration;
14998 
14999 			ed.dom.getParent(node, function(n) {
15000 				decoration = ed.dom.getStyle(n, 'text-decoration');
15001 				return decoration && decoration !== 'none';
15002 			});
15003 
15004 			return decoration;
15005 		};
15006 
15007 		var processUnderlineAndColor = function(node) {
15008 			var textDecoration;
15009 			if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
15010 				textDecoration = getTextDecoration(node.parentNode);
15011 				if (ed.dom.getStyle(node, 'color') && textDecoration) {
15012 					ed.dom.setStyle(node, 'text-decoration', textDecoration);
15013 				} else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
15014 					ed.dom.setStyle(node, 'text-decoration', null);
15015 				}
15016 			}
15017 		};
15018 
15019 		function apply(name, vars, node) {
15020 			var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
15021 
15022 			function setElementFormat(elm, fmt) {
15023 				fmt = fmt || format;
15024 
15025 				if (elm) {
15026 					if (fmt.onformat) {
15027 						fmt.onformat(elm, fmt, vars, node);
15028 					}
15029 
15030 					each(fmt.styles, function(value, name) {
15031 						dom.setStyle(elm, name, replaceVars(value, vars));
15032 					});
15033 
15034 					each(fmt.attributes, function(value, name) {
15035 						dom.setAttrib(elm, name, replaceVars(value, vars));
15036 					});
15037 
15038 					each(fmt.classes, function(value) {
15039 						value = replaceVars(value, vars);
15040 
15041 						if (!dom.hasClass(elm, value))
15042 							dom.addClass(elm, value);
15043 					});
15044 				}
15045 			};
15046 			function adjustSelectionToVisibleSelection() {
15047 				function findSelectionEnd(start, end) {
15048 					var walker = new TreeWalker(end);
15049 					for (node = walker.current(); node; node = walker.prev()) {
15050 						if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
15051 							return node;
15052 						}
15053 					}
15054 				};
15055 
15056 				// Adjust selection so that a end container with a end offset of zero is not included in the selection
15057 				// as this isn't visible to the user.
15058 				var rng = ed.selection.getRng();
15059 				var start = rng.startContainer;
15060 				var end = rng.endContainer;
15061 
15062 				if (start != end && rng.endOffset === 0) {
15063 					var newEnd = findSelectionEnd(start, end);
15064 					var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
15065 
15066 					rng.setEnd(newEnd, endOffset);
15067 				}
15068 
15069 				return rng;
15070 			}
15071 			
15072 			function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
15073 				var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
15074 				
15075 				// find the index of the first child list.
15076 				each(node.childNodes, function(n, index) {
15077 					if (n.nodeName === "UL" || n.nodeName === "OL") {
15078 						listIndex = index;
15079 						list = n;
15080 						return false;
15081 					}
15082 				});
15083 				
15084 				// get the index of the bookmarks
15085 				each(node.childNodes, function(n, index) {
15086 					if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
15087 						if (n.id == bookmark.id + "_start") {
15088 							startIndex = index;
15089 						} else if (n.id == bookmark.id + "_end") {
15090 							endIndex = index;
15091 						}
15092 					}
15093 				});
15094 				
15095 				// if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
15096 				if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
15097 					each(tinymce.grep(node.childNodes), process);
15098 					return 0;
15099 				} else {
15100 					currentWrapElm = dom.clone(wrapElm, FALSE);
15101 
15102 					// create a list of the nodes on the same side of the list as the selection
15103 					each(tinymce.grep(node.childNodes), function(n, index) {
15104 						if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
15105 							nodes.push(n); 
15106 							n.parentNode.removeChild(n);
15107 						}
15108 					});
15109 
15110 					// insert the wrapping element either before or after the list.
15111 					if (startIndex < listIndex) {
15112 						node.insertBefore(currentWrapElm, list);
15113 					} else if (startIndex > listIndex) {
15114 						node.insertBefore(currentWrapElm, list.nextSibling);
15115 					}
15116 					
15117 					// add the new nodes to the list.
15118 					newWrappers.push(currentWrapElm);
15119 
15120 					each(nodes, function(node) {
15121 						currentWrapElm.appendChild(node);
15122 					});
15123 
15124 					return currentWrapElm;
15125 				}
15126 			};
15127 
15128 			function applyRngStyle(rng, bookmark, node_specific) {
15129 				var newWrappers = [], wrapName, wrapElm, contentEditable = true;
15130 
15131 				// Setup wrapper element
15132 				wrapName = format.inline || format.block;
15133 				wrapElm = dom.create(wrapName);
15134 				setElementFormat(wrapElm);
15135 
15136 				rangeUtils.walk(rng, function(nodes) {
15137 					var currentWrapElm;
15138 
15139 					function process(node) {
15140 						var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
15141 
15142 						lastContentEditable = contentEditable;
15143 						nodeName = node.nodeName.toLowerCase();
15144 						parentName = node.parentNode.nodeName.toLowerCase();
15145 
15146 						// Node has a contentEditable value
15147 						if (node.nodeType === 1 && getContentEditable(node)) {
15148 							lastContentEditable = contentEditable;
15149 							contentEditable = getContentEditable(node) === "true";
15150 							hasContentEditableState = true; // We don't want to wrap the container only it's children
15151 						}
15152 
15153 						// Stop wrapping on br elements
15154 						if (isEq(nodeName, 'br')) {
15155 							currentWrapElm = 0;
15156 
15157 							// Remove any br elements when we wrap things
15158 							if (format.block)
15159 								dom.remove(node);
15160 
15161 							return;
15162 						}
15163 
15164 						// If node is wrapper type
15165 						if (format.wrapper && matchNode(node, name, vars)) {
15166 							currentWrapElm = 0;
15167 							return;
15168 						}
15169 
15170 						// Can we rename the block
15171 						if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) {
15172 							node = dom.rename(node, wrapName);
15173 							setElementFormat(node);
15174 							newWrappers.push(node);
15175 							currentWrapElm = 0;
15176 							return;
15177 						}
15178 
15179 						// Handle selector patterns
15180 						if (format.selector) {
15181 							// Look for matching formats
15182 							each(formatList, function(format) {
15183 								// Check collapsed state if it exists
15184 								if ('collapsed' in format && format.collapsed !== isCollapsed) {
15185 									return;
15186 								}
15187 
15188 								if (dom.is(node, format.selector) && !isCaretNode(node)) {
15189 									setElementFormat(node, format);
15190 									found = true;
15191 								}
15192 							});
15193 
15194 							// Continue processing if a selector match wasn't found and a inline element is defined
15195 							if (!format.inline || found) {
15196 								currentWrapElm = 0;
15197 								return;
15198 							}
15199 						}
15200 
15201 						// Is it valid to wrap this item
15202 						if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
15203 								!(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) {
15204 							// Start wrapping
15205 							if (!currentWrapElm) {
15206 								// Wrap the node
15207 								currentWrapElm = dom.clone(wrapElm, FALSE);
15208 								node.parentNode.insertBefore(currentWrapElm, node);
15209 								newWrappers.push(currentWrapElm);
15210 							}
15211 
15212 							currentWrapElm.appendChild(node);
15213 						} else if (nodeName == 'li' && bookmark) {
15214 							// Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.
15215 							currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
15216 						} else {
15217 							// Start a new wrapper for possible children
15218 							currentWrapElm = 0;
15219 							
15220 							each(tinymce.grep(node.childNodes), process);
15221 
15222 							if (hasContentEditableState) {
15223 								contentEditable = lastContentEditable; // Restore last contentEditable state from stack
15224 							}
15225 
15226 							// End the last wrapper
15227 							currentWrapElm = 0;
15228 						}
15229 					};
15230 
15231 					// Process siblings from range
15232 					each(nodes, process);
15233 				});
15234 
15235 				// Wrap links inside as well, for example color inside a link when the wrapper is around the link
15236 				if (format.wrap_links === false) {
15237 					each(newWrappers, function(node) {
15238 						function process(node) {
15239 							var i, currentWrapElm, children;
15240 
15241 							if (node.nodeName === 'A') {
15242 								currentWrapElm = dom.clone(wrapElm, FALSE);
15243 								newWrappers.push(currentWrapElm);
15244 
15245 								children = tinymce.grep(node.childNodes);
15246 								for (i = 0; i < children.length; i++)
15247 									currentWrapElm.appendChild(children[i]);
15248 
15249 								node.appendChild(currentWrapElm);
15250 							}
15251 
15252 							each(tinymce.grep(node.childNodes), process);
15253 						};
15254 
15255 						process(node);
15256 					});
15257 				}
15258 
15259 				// Cleanup
15260 				
15261 				each(newWrappers, function(node) {
15262 					var childCount;
15263 
15264 					function getChildCount(node) {
15265 						var count = 0;
15266 
15267 						each(node.childNodes, function(node) {
15268 							if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
15269 								count++;
15270 						});
15271 
15272 						return count;
15273 					};
15274 
15275 					function mergeStyles(node) {
15276 						var child, clone;
15277 
15278 						each(node.childNodes, function(node) {
15279 							if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
15280 								child = node;
15281 								return FALSE; // break loop
15282 							}
15283 						});
15284 
15285 						// If child was found and of the same type as the current node
15286 						if (child && matchName(child, format)) {
15287 							clone = dom.clone(child, FALSE);
15288 							setElementFormat(clone);
15289 
15290 							dom.replace(clone, node, TRUE);
15291 							dom.remove(child, 1);
15292 						}
15293 
15294 						return clone || node;
15295 					};
15296 
15297 					childCount = getChildCount(node);
15298 
15299 					// Remove empty nodes but only if there is multiple wrappers and they are not block
15300 					// elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
15301 					if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
15302 						dom.remove(node, 1);
15303 						return;
15304 					}
15305 
15306 					if (format.inline || format.wrapper) {
15307 						// Merges the current node with it's children of similar type to reduce the number of elements
15308 						if (!format.exact && childCount === 1)
15309 							node = mergeStyles(node);
15310 
15311 						// Remove/merge children
15312 						each(formatList, function(format) {
15313 							// Merge all children of similar type will move styles from child to parent
15314 							// this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
15315 							// will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
15316 							each(dom.select(format.inline, node), function(child) {
15317 								var parent;
15318 
15319 								// When wrap_links is set to false we don't want
15320 								// to remove the format on children within links
15321 								if (format.wrap_links === false) {
15322 									parent = child.parentNode;
15323 
15324 									do {
15325 										if (parent.nodeName === 'A')
15326 											return;
15327 									} while (parent = parent.parentNode);
15328 								}
15329 
15330 								removeFormat(format, vars, child, format.exact ? child : null);
15331 							});
15332 						});
15333 
15334 						// Remove child if direct parent is of same type
15335 						if (matchNode(node.parentNode, name, vars)) {
15336 							dom.remove(node, 1);
15337 							node = 0;
15338 							return TRUE;
15339 						}
15340 
15341 						// Look for parent with similar style format
15342 						if (format.merge_with_parents) {
15343 							dom.getParent(node.parentNode, function(parent) {
15344 								if (matchNode(parent, name, vars)) {
15345 									dom.remove(node, 1);
15346 									node = 0;
15347 									return TRUE;
15348 								}
15349 							});
15350 						}
15351 
15352 						// Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
15353 						if (node && format.merge_siblings !== false) {
15354 							node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
15355 							node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
15356 						}
15357 					}
15358 				});
15359 			};
15360 
15361 			if (format) {
15362 				if (node) {
15363 					if (node.nodeType) {
15364 						rng = dom.createRng();
15365 						rng.setStartBefore(node);
15366 						rng.setEndAfter(node);
15367 						applyRngStyle(expandRng(rng, formatList), null, true);
15368 					} else {
15369 						applyRngStyle(node, null, true);
15370 					}
15371 				} else {
15372 					if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
15373 						// Obtain selection node before selection is unselected by applyRngStyle()
15374 						var curSelNode = ed.selection.getNode();
15375 
15376 						// If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false
15377 						// It's kind of a hack but people should be using the default block type P since all desktop editors work that way
15378 						if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
15379 							apply(formatList[0].defaultBlock);
15380 						}
15381 
15382 						// Apply formatting to selection
15383 						ed.selection.setRng(adjustSelectionToVisibleSelection());
15384 						bookmark = selection.getBookmark();
15385 						applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
15386 
15387 						// Colored nodes should be underlined so that the color of the underline matches the text color.
15388 						if (format.styles && (format.styles.color || format.styles.textDecoration)) {
15389 							tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
15390 							processUnderlineAndColor(curSelNode);
15391 						}
15392 
15393 						selection.moveToBookmark(bookmark);
15394 						moveStart(selection.getRng(TRUE));
15395 						ed.nodeChanged();
15396 					} else
15397 						performCaretAction('apply', name, vars);
15398 				}
15399 			}
15400 		};
15401 
15402 		function remove(name, vars, node) {
15403 			var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true;
15404 
15405 			// Merges the styles for each node
15406 			function process(node) {
15407 				var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState;
15408 
15409 				// Node has a contentEditable value
15410 				if (node.nodeType === 1 && getContentEditable(node)) {
15411 					lastContentEditable = contentEditable;
15412 					contentEditable = getContentEditable(node) === "true";
15413 					hasContentEditableState = true; // We don't want to wrap the container only it's children
15414 				}
15415 
15416 				// Grab the children first since the nodelist might be changed
15417 				children = tinymce.grep(node.childNodes);
15418 
15419 				// Process current node
15420 				if (contentEditable && !hasContentEditableState) {
15421 					for (i = 0, l = formatList.length; i < l; i++) {
15422 						if (removeFormat(formatList[i], vars, node, node))
15423 							break;
15424 					}
15425 				}
15426 
15427 				// Process the children
15428 				if (format.deep) {
15429 					if (children.length) {					
15430 						for (i = 0, l = children.length; i < l; i++)
15431 							process(children[i]);
15432 
15433 						if (hasContentEditableState) {
15434 							contentEditable = lastContentEditable; // Restore last contentEditable state from stack
15435 						}
15436 					}
15437 				}
15438 			};
15439 
15440 			function findFormatRoot(container) {
15441 				var formatRoot;
15442 
15443 				// Find format root
15444 				each(getParents(container.parentNode).reverse(), function(parent) {
15445 					var format;
15446 
15447 					// Find format root element
15448 					if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
15449 						// Is the node matching the format we are looking for
15450 						format = matchNode(parent, name, vars);
15451 						if (format && format.split !== false)
15452 							formatRoot = parent;
15453 					}
15454 				});
15455 
15456 				return formatRoot;
15457 			};
15458 
15459 			function wrapAndSplit(format_root, container, target, split) {
15460 				var parent, clone, lastClone, firstClone, i, formatRootParent;
15461 
15462 				// Format root found then clone formats and split it
15463 				if (format_root) {
15464 					formatRootParent = format_root.parentNode;
15465 
15466 					for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
15467 						clone = dom.clone(parent, FALSE);
15468 
15469 						for (i = 0; i < formatList.length; i++) {
15470 							if (removeFormat(formatList[i], vars, clone, clone)) {
15471 								clone = 0;
15472 								break;
15473 							}
15474 						}
15475 
15476 						// Build wrapper node
15477 						if (clone) {
15478 							if (lastClone)
15479 								clone.appendChild(lastClone);
15480 
15481 							if (!firstClone)
15482 								firstClone = clone;
15483 
15484 							lastClone = clone;
15485 						}
15486 					}
15487 
15488 					// Never split block elements if the format is mixed
15489 					if (split && (!format.mixed || !isBlock(format_root)))
15490 						container = dom.split(format_root, container);
15491 
15492 					// Wrap container in cloned formats
15493 					if (lastClone) {
15494 						target.parentNode.insertBefore(lastClone, target);
15495 						firstClone.appendChild(target);
15496 					}
15497 				}
15498 
15499 				return container;
15500 			};
15501 
15502 			function splitToFormatRoot(container) {
15503 				return wrapAndSplit(findFormatRoot(container), container, container, true);
15504 			};
15505 
15506 			function unwrap(start) {
15507 				var node = dom.get(start ? '_start' : '_end'),
15508 					out = node[start ? 'firstChild' : 'lastChild'];
15509 
15510 				// If the end is placed within the start the result will be removed
15511 				// So this checks if the out node is a bookmark node if it is it
15512 				// checks for another more suitable node
15513 				if (isBookmarkNode(out))
15514 					out = out[start ? 'firstChild' : 'lastChild'];
15515 
15516 				dom.remove(node, true);
15517 
15518 				return out;
15519 			};
15520 
15521 			function removeRngStyle(rng) {
15522 				var startContainer, endContainer, node;
15523 
15524 				rng = expandRng(rng, formatList, TRUE);
15525 
15526 				if (format.split) {
15527 					startContainer = getContainer(rng, TRUE);
15528 					endContainer = getContainer(rng);
15529 
15530 					if (startContainer != endContainer) {
15531 						// WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead
15532 						// This will happen if you tripple click a table cell and use remove formatting
15533 						if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
15534 							startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer;
15535 						}
15536 
15537 						// Wrap start/end nodes in span element since these might be cloned/moved
15538 						startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
15539 						endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
15540 
15541 						// Split start/end
15542 						splitToFormatRoot(startContainer);
15543 						splitToFormatRoot(endContainer);
15544 
15545 						// Unwrap start/end to get real elements again
15546 						startContainer = unwrap(TRUE);
15547 						endContainer = unwrap();
15548 					} else
15549 						startContainer = endContainer = splitToFormatRoot(startContainer);
15550 
15551 					// Update range positions since they might have changed after the split operations
15552 					rng.startContainer = startContainer.parentNode;
15553 					rng.startOffset = nodeIndex(startContainer);
15554 					rng.endContainer = endContainer.parentNode;
15555 					rng.endOffset = nodeIndex(endContainer) + 1;
15556 				}
15557 
15558 				// Remove items between start/end
15559 				rangeUtils.walk(rng, function(nodes) {
15560 					each(nodes, function(node) {
15561 						process(node);
15562 
15563 						// Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
15564 						if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
15565 							removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
15566 						}
15567 					});
15568 				});
15569 			};
15570 
15571 			// Handle node
15572 			if (node) {
15573 				if (node.nodeType) {
15574 					rng = dom.createRng();
15575 					rng.setStartBefore(node);
15576 					rng.setEndAfter(node);
15577 					removeRngStyle(rng);
15578 				} else {
15579 					removeRngStyle(node);
15580 				}
15581 
15582 				return;
15583 			}
15584 
15585 			if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
15586 				bookmark = selection.getBookmark();
15587 				removeRngStyle(selection.getRng(TRUE));
15588 				selection.moveToBookmark(bookmark);
15589 
15590 				// Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
15591 				if (format.inline && match(name, vars, selection.getStart())) {
15592 					moveStart(selection.getRng(true));
15593 				}
15594 
15595 				ed.nodeChanged();
15596 			} else
15597 				performCaretAction('remove', name, vars);
15598 		};
15599 
15600 		function toggle(name, vars, node) {
15601 			var fmt = get(name);
15602 
15603 			if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle))
15604 				remove(name, vars, node);
15605 			else
15606 				apply(name, vars, node);
15607 		};
15608 
15609 		function matchNode(node, name, vars, similar) {
15610 			var formatList = get(name), format, i, classes;
15611 
15612 			function matchItems(node, format, item_name) {
15613 				var key, value, items = format[item_name], i;
15614 
15615 				// Custom match
15616 				if (format.onmatch) {
15617 					return format.onmatch(node, format, item_name);
15618 				}
15619 
15620 				// Check all items
15621 				if (items) {
15622 					// Non indexed object
15623 					if (items.length === undef) {
15624 						for (key in items) {
15625 							if (items.hasOwnProperty(key)) {
15626 								if (item_name === 'attributes')
15627 									value = dom.getAttrib(node, key);
15628 								else
15629 									value = getStyle(node, key);
15630 
15631 								if (similar && !value && !format.exact)
15632 									return;
15633 
15634 								if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
15635 									return;
15636 							}
15637 						}
15638 					} else {
15639 						// Only one match needed for indexed arrays
15640 						for (i = 0; i < items.length; i++) {
15641 							if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
15642 								return format;
15643 						}
15644 					}
15645 				}
15646 
15647 				return format;
15648 			};
15649 
15650 			if (formatList && node) {
15651 				// Check each format in list
15652 				for (i = 0; i < formatList.length; i++) {
15653 					format = formatList[i];
15654 
15655 					// Name name, attributes, styles and classes
15656 					if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
15657 						// Match classes
15658 						if (classes = format.classes) {
15659 							for (i = 0; i < classes.length; i++) {
15660 								if (!dom.hasClass(node, classes[i]))
15661 									return;
15662 							}
15663 						}
15664 
15665 						return format;
15666 					}
15667 				}
15668 			}
15669 		};
15670 
15671 		function match(name, vars, node) {
15672 			var startNode;
15673 
15674 			function matchParents(node) {
15675 				// Find first node with similar format settings
15676 				node = dom.getParent(node, function(node) {
15677 					return !!matchNode(node, name, vars, true);
15678 				});
15679 
15680 				// Do an exact check on the similar format element
15681 				return matchNode(node, name, vars);
15682 			};
15683 
15684 			// Check specified node
15685 			if (node)
15686 				return matchParents(node);
15687 
15688 			// Check selected node
15689 			node = selection.getNode();
15690 			if (matchParents(node))
15691 				return TRUE;
15692 
15693 			// Check start node if it's different
15694 			startNode = selection.getStart();
15695 			if (startNode != node) {
15696 				if (matchParents(startNode))
15697 					return TRUE;
15698 			}
15699 
15700 			return FALSE;
15701 		};
15702 
15703 		function matchAll(names, vars) {
15704 			var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
15705 
15706 			// Check start of selection for formats
15707 			startElement = selection.getStart();
15708 			dom.getParent(startElement, function(node) {
15709 				var i, name;
15710 
15711 				for (i = 0; i < names.length; i++) {
15712 					name = names[i];
15713 
15714 					if (!checkedMap[name] && matchNode(node, name, vars)) {
15715 						checkedMap[name] = true;
15716 						matchedFormatNames.push(name);
15717 					}
15718 				}
15719 			}, dom.getRoot());
15720 
15721 			return matchedFormatNames;
15722 		};
15723 
15724 		function canApply(name) {
15725 			var formatList = get(name), startNode, parents, i, x, selector;
15726 
15727 			if (formatList) {
15728 				startNode = selection.getStart();
15729 				parents = getParents(startNode);
15730 
15731 				for (x = formatList.length - 1; x >= 0; x--) {
15732 					selector = formatList[x].selector;
15733 
15734 					// Format is not selector based, then always return TRUE
15735 					if (!selector)
15736 						return TRUE;
15737 
15738 					for (i = parents.length - 1; i >= 0; i--) {
15739 						if (dom.is(parents[i], selector))
15740 							return TRUE;
15741 					}
15742 				}
15743 			}
15744 
15745 			return FALSE;
15746 		};
15747 
15748 		function formatChanged(formats, callback) {
15749 			var currentFormats;
15750 
15751 			// Setup format node change logic
15752 			if (!formatChangeData) {
15753 				formatChangeData = {};
15754 				currentFormats = {};
15755 
15756 				ed.onNodeChange.addToTop(function(ed, cm, node) {
15757 					var parents = getParents(node), matchedFormats = {};
15758 
15759 					// Check for new formats
15760 					each(formatChangeData, function(callbacks, format) {
15761 						each(parents, function(node) {
15762 							if (matchNode(node, format, {}, true)) {
15763 								if (!currentFormats[format]) {
15764 									// Execute callbacks
15765 									each(callbacks, function(callback) {
15766 										callback(true, {node: node, format: format, parents: parents});
15767 									});
15768 
15769 									currentFormats[format] = callbacks;
15770 								}
15771 
15772 								matchedFormats[format] = callbacks;
15773 								return false;
15774 							}
15775 						});
15776 					});
15777 
15778 					// Check if current formats still match
15779 					each(currentFormats, function(callbacks, format) {
15780 						if (!matchedFormats[format]) {
15781 							delete currentFormats[format];
15782 
15783 							each(callbacks, function(callback) {
15784 								callback(false, {node: node, format: format, parents: parents});
15785 							});
15786 						}
15787 					});
15788 				});
15789 			}
15790 
15791 			// Add format listeners
15792 			each(formats.split(','), function(format) {
15793 				if (!formatChangeData[format]) {
15794 					formatChangeData[format] = [];
15795 				}
15796 
15797 				formatChangeData[format].push(callback);
15798 			});
15799 
15800 			return this;
15801 		};
15802 
15803 		// Expose to public
15804 		tinymce.extend(this, {
15805 			get : get,
15806 			register : register,
15807 			apply : apply,
15808 			remove : remove,
15809 			toggle : toggle,
15810 			match : match,
15811 			matchAll : matchAll,
15812 			matchNode : matchNode,
15813 			canApply : canApply,
15814 			formatChanged: formatChanged
15815 		});
15816 
15817 		// Initialize
15818 		defaultFormats();
15819 		addKeyboardShortcuts();
15820 
15821 		// Private functions
15822 
15823 		function matchName(node, format) {
15824 			// Check for inline match
15825 			if (isEq(node, format.inline))
15826 				return TRUE;
15827 
15828 			// Check for block match
15829 			if (isEq(node, format.block))
15830 				return TRUE;
15831 
15832 			// Check for selector match
15833 			if (format.selector)
15834 				return dom.is(node, format.selector);
15835 		};
15836 
15837 		function isEq(str1, str2) {
15838 			str1 = str1 || '';
15839 			str2 = str2 || '';
15840 
15841 			str1 = '' + (str1.nodeName || str1);
15842 			str2 = '' + (str2.nodeName || str2);
15843 
15844 			return str1.toLowerCase() == str2.toLowerCase();
15845 		};
15846 
15847 		function getStyle(node, name) {
15848 			var styleVal = dom.getStyle(node, name);
15849 
15850 			// Force the format to hex
15851 			if (name == 'color' || name == 'backgroundColor')
15852 				styleVal = dom.toHex(styleVal);
15853 
15854 			// Opera will return bold as 700
15855 			if (name == 'fontWeight' && styleVal == 700)
15856 				styleVal = 'bold';
15857 
15858 			return '' + styleVal;
15859 		};
15860 
15861 		function replaceVars(value, vars) {
15862 			if (typeof(value) != "string")
15863 				value = value(vars);
15864 			else if (vars) {
15865 				value = value.replace(/%(\w+)/g, function(str, name) {
15866 					return vars[name] || str;
15867 				});
15868 			}
15869 
15870 			return value;
15871 		};
15872 
15873 		function isWhiteSpaceNode(node) {
15874 			return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
15875 		};
15876 
15877 		function wrap(node, name, attrs) {
15878 			var wrapper = dom.create(name, attrs);
15879 
15880 			node.parentNode.insertBefore(wrapper, node);
15881 			wrapper.appendChild(node);
15882 
15883 			return wrapper;
15884 		};
15885 
15886 		function expandRng(rng, format, remove) {
15887 			var sibling, lastIdx, leaf, endPoint,
15888 				startContainer = rng.startContainer,
15889 				startOffset = rng.startOffset,
15890 				endContainer = rng.endContainer,
15891 				endOffset = rng.endOffset;
15892 
15893 			// This function walks up the tree if there is no siblings before/after the node
15894 			function findParentContainer(start) {
15895 				var container, parent, child, sibling, siblingName, root;
15896 
15897 				container = parent = start ? startContainer : endContainer;
15898 				siblingName = start ? 'previousSibling' : 'nextSibling';
15899 				root = dom.getRoot();
15900 
15901 				// If it's a text node and the offset is inside the text
15902 				if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
15903 					if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
15904 						return container;
15905 					}
15906 				}
15907 
15908 				for (;;) {
15909 					// Stop expanding on block elements
15910 					if (!format[0].block_expand && isBlock(parent))
15911 						return parent;
15912 
15913 					// Walk left/right
15914 					for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
15915 						if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) {
15916 							return parent;
15917 						}
15918 					}
15919 
15920 					// Check if we can move up are we at root level or body level
15921 					if (parent.parentNode == root) {
15922 						container = parent;
15923 						break;
15924 					}
15925 
15926 					parent = parent.parentNode;
15927 				}
15928 
15929 				return container;
15930 			};
15931 
15932 			// This function walks down the tree to find the leaf at the selection.
15933 			// The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
15934 			function findLeaf(node, offset) {
15935 				if (offset === undef)
15936 					offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15937 				while (node && node.hasChildNodes()) {
15938 					node = node.childNodes[offset];
15939 					if (node)
15940 						offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15941 				}
15942 				return { node: node, offset: offset };
15943 			}
15944 
15945 			// If index based start position then resolve it
15946 			if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
15947 				lastIdx = startContainer.childNodes.length - 1;
15948 				startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
15949 
15950 				if (startContainer.nodeType == 3)
15951 					startOffset = 0;
15952 			}
15953 
15954 			// If index based end position then resolve it
15955 			if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
15956 				lastIdx = endContainer.childNodes.length - 1;
15957 				endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
15958 
15959 				if (endContainer.nodeType == 3)
15960 					endOffset = endContainer.nodeValue.length;
15961 			}
15962 
15963 			// Expands the node to the closes contentEditable false element if it exists
15964 			function findParentContentEditable(node) {
15965 				var parent = node;
15966 
15967 				while (parent) {
15968 					if (parent.nodeType === 1 && getContentEditable(parent)) {
15969 						return getContentEditable(parent) === "false" ? parent : node;
15970 					}
15971 
15972 					parent = parent.parentNode;
15973 				}
15974 
15975 				return node;
15976 			};
15977 
15978 			function findWordEndPoint(container, offset, start) {
15979 				var walker, node, pos, lastTextNode;
15980 
15981 				function findSpace(node, offset) {
15982 					var pos, pos2, str = node.nodeValue;
15983 
15984 					if (typeof(offset) == "undefined") {
15985 						offset = start ? str.length : 0;
15986 					}
15987 
15988 					if (start) {
15989 						pos = str.lastIndexOf(' ', offset);
15990 						pos2 = str.lastIndexOf('\u00a0', offset);
15991 						pos = pos > pos2 ? pos : pos2;
15992 
15993 						// Include the space on remove to avoid tag soup
15994 						if (pos !== -1 && !remove) {
15995 							pos++;
15996 						}
15997 					} else {
15998 						pos = str.indexOf(' ', offset);
15999 						pos2 = str.indexOf('\u00a0', offset);
16000 						pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
16001 					}
16002 
16003 					return pos;
16004 				};
16005 
16006 				if (container.nodeType === 3) {
16007 					pos = findSpace(container, offset);
16008 
16009 					if (pos !== -1) {
16010 						return {container : container, offset : pos};
16011 					}
16012 
16013 					lastTextNode = container;
16014 				}
16015 
16016 				// Walk the nodes inside the block
16017 				walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
16018 				while (node = walker[start ? 'prev' : 'next']()) {
16019 					if (node.nodeType === 3) {
16020 						lastTextNode = node;
16021 						pos = findSpace(node);
16022 
16023 						if (pos !== -1) {
16024 							return {container : node, offset : pos};
16025 						}
16026 					} else if (isBlock(node)) {
16027 						break;
16028 					}
16029 				}
16030 
16031 				if (lastTextNode) {
16032 					if (start) {
16033 						offset = 0;
16034 					} else {
16035 						offset = lastTextNode.length;
16036 					}
16037 
16038 					return {container: lastTextNode, offset: offset};
16039 				}
16040 			};
16041 
16042 			function findSelectorEndPoint(container, sibling_name) {
16043 				var parents, i, y, curFormat;
16044 
16045 				if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name])
16046 					container = container[sibling_name];
16047 
16048 				parents = getParents(container);
16049 				for (i = 0; i < parents.length; i++) {
16050 					for (y = 0; y < format.length; y++) {
16051 						curFormat = format[y];
16052 
16053 						// If collapsed state is set then skip formats that doesn't match that
16054 						if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
16055 							continue;
16056 
16057 						if (dom.is(parents[i], curFormat.selector))
16058 							return parents[i];
16059 					}
16060 				}
16061 
16062 				return container;
16063 			};
16064 
16065 			function findBlockEndPoint(container, sibling_name, sibling_name2) {
16066 				var node;
16067 
16068 				// Expand to block of similar type
16069 				if (!format[0].wrapper)
16070 					node = dom.getParent(container, format[0].block);
16071 
16072 				// Expand to first wrappable block element or any block element
16073 				if (!node)
16074 					node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
16075 
16076 				// Exclude inner lists from wrapping
16077 				if (node && format[0].wrapper)
16078 					node = getParents(node, 'ul,ol').reverse()[0] || node;
16079 
16080 				// Didn't find a block element look for first/last wrappable element
16081 				if (!node) {
16082 					node = container;
16083 
16084 					while (node[sibling_name] && !isBlock(node[sibling_name])) {
16085 						node = node[sibling_name];
16086 
16087 						// Break on BR but include it will be removed later on
16088 						// we can't remove it now since we need to check if it can be wrapped
16089 						if (isEq(node, 'br'))
16090 							break;
16091 					}
16092 				}
16093 
16094 				return node || container;
16095 			};
16096 
16097 			// Expand to closest contentEditable element
16098 			startContainer = findParentContentEditable(startContainer);
16099 			endContainer = findParentContentEditable(endContainer);
16100 
16101 			// Exclude bookmark nodes if possible
16102 			if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
16103 				startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
16104 				startContainer = startContainer.nextSibling || startContainer;
16105 
16106 				if (startContainer.nodeType == 3)
16107 					startOffset = 0;
16108 			}
16109 
16110 			if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
16111 				endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
16112 				endContainer = endContainer.previousSibling || endContainer;
16113 
16114 				if (endContainer.nodeType == 3)
16115 					endOffset = endContainer.length;
16116 			}
16117 
16118 			if (format[0].inline) {
16119 				if (rng.collapsed) {
16120 					// Expand left to closest word boundery
16121 					endPoint = findWordEndPoint(startContainer, startOffset, true);
16122 					if (endPoint) {
16123 						startContainer = endPoint.container;
16124 						startOffset = endPoint.offset;
16125 					}
16126 
16127 					// Expand right to closest word boundery
16128 					endPoint = findWordEndPoint(endContainer, endOffset);
16129 					if (endPoint) {
16130 						endContainer = endPoint.container;
16131 						endOffset = endPoint.offset;
16132 					}
16133 				}
16134 
16135 				// Avoid applying formatting to a trailing space.
16136 				leaf = findLeaf(endContainer, endOffset);
16137 				if (leaf.node) {
16138 					while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
16139 						leaf = findLeaf(leaf.node.previousSibling);
16140 
16141 					if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
16142 							leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
16143 
16144 						if (leaf.offset > 1) {
16145 							endContainer = leaf.node;
16146 							endContainer.splitText(leaf.offset - 1);
16147 						}
16148 					}
16149 				}
16150 			}
16151 
16152 			// Move start/end point up the tree if the leaves are sharp and if we are in different containers
16153 			// Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
16154 			// This will reduce the number of wrapper elements that needs to be created
16155 			// Move start point up the tree
16156 			if (format[0].inline || format[0].block_expand) {
16157 				if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
16158 					startContainer = findParentContainer(true);
16159 				}
16160 
16161 				if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
16162 					endContainer = findParentContainer();
16163 				}
16164 			}
16165 
16166 			// Expand start/end container to matching selector
16167 			if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
16168 				// Find new startContainer/endContainer if there is better one
16169 				startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
16170 				endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
16171 			}
16172 
16173 			// Expand start/end container to matching block element or text node
16174 			if (format[0].block || format[0].selector) {
16175 				// Find new startContainer/endContainer if there is better one
16176 				startContainer = findBlockEndPoint(startContainer, 'previousSibling');
16177 				endContainer = findBlockEndPoint(endContainer, 'nextSibling');
16178 
16179 				// Non block element then try to expand up the leaf
16180 				if (format[0].block) {
16181 					if (!isBlock(startContainer))
16182 						startContainer = findParentContainer(true);
16183 
16184 					if (!isBlock(endContainer))
16185 						endContainer = findParentContainer();
16186 				}
16187 			}
16188 
16189 			// Setup index for startContainer
16190 			if (startContainer.nodeType == 1) {
16191 				startOffset = nodeIndex(startContainer);
16192 				startContainer = startContainer.parentNode;
16193 			}
16194 
16195 			// Setup index for endContainer
16196 			if (endContainer.nodeType == 1) {
16197 				endOffset = nodeIndex(endContainer) + 1;
16198 				endContainer = endContainer.parentNode;
16199 			}
16200 
16201 			// Return new range like object
16202 			return {
16203 				startContainer : startContainer,
16204 				startOffset : startOffset,
16205 				endContainer : endContainer,
16206 				endOffset : endOffset
16207 			};
16208 		}
16209 
16210 		function removeFormat(format, vars, node, compare_node) {
16211 			var i, attrs, stylesModified;
16212 
16213 			// Check if node matches format
16214 			if (!matchName(node, format))
16215 				return FALSE;
16216 
16217 			// Should we compare with format attribs and styles
16218 			if (format.remove != 'all') {
16219 				// Remove styles
16220 				each(format.styles, function(value, name) {
16221 					value = replaceVars(value, vars);
16222 
16223 					// Indexed array
16224 					if (typeof(name) === 'number') {
16225 						name = value;
16226 						compare_node = 0;
16227 					}
16228 
16229 					if (!compare_node || isEq(getStyle(compare_node, name), value))
16230 						dom.setStyle(node, name, '');
16231 
16232 					stylesModified = 1;
16233 				});
16234 
16235 				// Remove style attribute if it's empty
16236 				if (stylesModified && dom.getAttrib(node, 'style') == '') {
16237 					node.removeAttribute('style');
16238 					node.removeAttribute('data-mce-style');
16239 				}
16240 
16241 				// Remove attributes
16242 				each(format.attributes, function(value, name) {
16243 					var valueOut;
16244 
16245 					value = replaceVars(value, vars);
16246 
16247 					// Indexed array
16248 					if (typeof(name) === 'number') {
16249 						name = value;
16250 						compare_node = 0;
16251 					}
16252 
16253 					if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
16254 						// Keep internal classes
16255 						if (name == 'class') {
16256 							value = dom.getAttrib(node, name);
16257 							if (value) {
16258 								// Build new class value where everything is removed except the internal prefixed classes
16259 								valueOut = '';
16260 								each(value.split(/\s+/), function(cls) {
16261 									if (/mce\w+/.test(cls))
16262 										valueOut += (valueOut ? ' ' : '') + cls;
16263 								});
16264 
16265 								// We got some internal classes left
16266 								if (valueOut) {
16267 									dom.setAttrib(node, name, valueOut);
16268 									return;
16269 								}
16270 							}
16271 						}
16272 
16273 						// IE6 has a bug where the attribute doesn't get removed correctly
16274 						if (name == "class")
16275 							node.removeAttribute('className');
16276 
16277 						// Remove mce prefixed attributes
16278 						if (MCE_ATTR_RE.test(name))
16279 							node.removeAttribute('data-mce-' + name);
16280 
16281 						node.removeAttribute(name);
16282 					}
16283 				});
16284 
16285 				// Remove classes
16286 				each(format.classes, function(value) {
16287 					value = replaceVars(value, vars);
16288 
16289 					if (!compare_node || dom.hasClass(compare_node, value))
16290 						dom.removeClass(node, value);
16291 				});
16292 
16293 				// Check for non internal attributes
16294 				attrs = dom.getAttribs(node);
16295 				for (i = 0; i < attrs.length; i++) {
16296 					if (attrs[i].nodeName.indexOf('_') !== 0)
16297 						return FALSE;
16298 				}
16299 			}
16300 
16301 			// Remove the inline child if it's empty for example <b> or <span>
16302 			if (format.remove != 'none') {
16303 				removeNode(node, format);
16304 				return TRUE;
16305 			}
16306 		};
16307 
16308 		function removeNode(node, format) {
16309 			var parentNode = node.parentNode, rootBlockElm;
16310 
16311 			function find(node, next, inc) {
16312 				node = getNonWhiteSpaceSibling(node, next, inc);
16313 
16314 				return !node || (node.nodeName == 'BR' || isBlock(node));
16315 			};
16316 
16317 			if (format.block) {
16318 				if (!forcedRootBlock) {
16319 					// Append BR elements if needed before we remove the block
16320 					if (isBlock(node) && !isBlock(parentNode)) {
16321 						if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
16322 							node.insertBefore(dom.create('br'), node.firstChild);
16323 
16324 						if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
16325 							node.appendChild(dom.create('br'));
16326 					}
16327 				} else {
16328 					// Wrap the block in a forcedRootBlock if we are at the root of document
16329 					if (parentNode == dom.getRoot()) {
16330 						if (!format.list_block || !isEq(node, format.list_block)) {
16331 							each(tinymce.grep(node.childNodes), function(node) {
16332 								if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
16333 									if (!rootBlockElm)
16334 										rootBlockElm = wrap(node, forcedRootBlock);
16335 									else
16336 										rootBlockElm.appendChild(node);
16337 								} else
16338 									rootBlockElm = 0;
16339 							});
16340 						}
16341 					}
16342 				}
16343 			}
16344 
16345 			// Never remove nodes that isn't the specified inline element if a selector is specified too
16346 			if (format.selector && format.inline && !isEq(format.inline, node))
16347 				return;
16348 
16349 			dom.remove(node, 1);
16350 		};
16351 
16352 		function getNonWhiteSpaceSibling(node, next, inc) {
16353 			if (node) {
16354 				next = next ? 'nextSibling' : 'previousSibling';
16355 
16356 				for (node = inc ? node : node[next]; node; node = node[next]) {
16357 					if (node.nodeType == 1 || !isWhiteSpaceNode(node))
16358 						return node;
16359 				}
16360 			}
16361 		};
16362 
16363 		function isBookmarkNode(node) {
16364 			return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
16365 		};
16366 
16367 		function mergeSiblings(prev, next) {
16368 			var marker, sibling, tmpSibling;
16369 
16370 			function compareElements(node1, node2) {
16371 				// Not the same name
16372 				if (node1.nodeName != node2.nodeName)
16373 					return FALSE;
16374 
16375 				function getAttribs(node) {
16376 					var attribs = {};
16377 
16378 					each(dom.getAttribs(node), function(attr) {
16379 						var name = attr.nodeName.toLowerCase();
16380 
16381 						// Don't compare internal attributes or style
16382 						if (name.indexOf('_') !== 0 && name !== 'style')
16383 							attribs[name] = dom.getAttrib(node, name);
16384 					});
16385 
16386 					return attribs;
16387 				};
16388 
16389 				function compareObjects(obj1, obj2) {
16390 					var value, name;
16391 
16392 					for (name in obj1) {
16393 						// Obj1 has item obj2 doesn't have
16394 						if (obj1.hasOwnProperty(name)) {
16395 							value = obj2[name];
16396 
16397 							// Obj2 doesn't have obj1 item
16398 							if (value === undef)
16399 								return FALSE;
16400 
16401 							// Obj2 item has a different value
16402 							if (obj1[name] != value)
16403 								return FALSE;
16404 
16405 							// Delete similar value
16406 							delete obj2[name];
16407 						}
16408 					}
16409 
16410 					// Check if obj 2 has something obj 1 doesn't have
16411 					for (name in obj2) {
16412 						// Obj2 has item obj1 doesn't have
16413 						if (obj2.hasOwnProperty(name))
16414 							return FALSE;
16415 					}
16416 
16417 					return TRUE;
16418 				};
16419 
16420 				// Attribs are not the same
16421 				if (!compareObjects(getAttribs(node1), getAttribs(node2)))
16422 					return FALSE;
16423 
16424 				// Styles are not the same
16425 				if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
16426 					return FALSE;
16427 
16428 				return TRUE;
16429 			};
16430 
16431 			function findElementSibling(node, sibling_name) {
16432 				for (sibling = node; sibling; sibling = sibling[sibling_name]) {
16433 					if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
16434 						return node;
16435 
16436 					if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
16437 						return sibling;
16438 				}
16439 
16440 				return node;
16441 			};
16442 
16443 			// Check if next/prev exists and that they are elements
16444 			if (prev && next) {
16445 				// If previous sibling is empty then jump over it
16446 				prev = findElementSibling(prev, 'previousSibling');
16447 				next = findElementSibling(next, 'nextSibling');
16448 
16449 				// Compare next and previous nodes
16450 				if (compareElements(prev, next)) {
16451 					// Append nodes between
16452 					for (sibling = prev.nextSibling; sibling && sibling != next;) {
16453 						tmpSibling = sibling;
16454 						sibling = sibling.nextSibling;
16455 						prev.appendChild(tmpSibling);
16456 					}
16457 
16458 					// Remove next node
16459 					dom.remove(next);
16460 
16461 					// Move children into prev node
16462 					each(tinymce.grep(next.childNodes), function(node) {
16463 						prev.appendChild(node);
16464 					});
16465 
16466 					return prev;
16467 				}
16468 			}
16469 
16470 			return next;
16471 		};
16472 
16473 		function isTextBlock(name) {
16474 			return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
16475 		};
16476 
16477 		function getContainer(rng, start) {
16478 			var container, offset, lastIdx, walker;
16479 
16480 			container = rng[start ? 'startContainer' : 'endContainer'];
16481 			offset = rng[start ? 'startOffset' : 'endOffset'];
16482 
16483 			if (container.nodeType == 1) {
16484 				lastIdx = container.childNodes.length - 1;
16485 
16486 				if (!start && offset)
16487 					offset--;
16488 
16489 				container = container.childNodes[offset > lastIdx ? lastIdx : offset];
16490 			}
16491 
16492 			// If start text node is excluded then walk to the next node
16493 			if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
16494 				container = new TreeWalker(container, ed.getBody()).next() || container;
16495 			}
16496 
16497 			// If end text node is excluded then walk to the previous node
16498 			if (container.nodeType === 3 && !start && offset === 0) {
16499 				container = new TreeWalker(container, ed.getBody()).prev() || container;
16500 			}
16501 
16502 			return container;
16503 		};
16504 
16505 		function performCaretAction(type, name, vars) {
16506 			var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
16507 
16508 			// Creates a caret container bogus element
16509 			function createCaretContainer(fill) {
16510 				var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
16511 
16512 				if (fill) {
16513 					caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
16514 				}
16515 
16516 				return caretContainer;
16517 			};
16518 
16519 			function isCaretContainerEmpty(node, nodes) {
16520 				while (node) {
16521 					if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
16522 						return false;
16523 					}
16524 
16525 					// Collect nodes
16526 					if (nodes && node.nodeType === 1) {
16527 						nodes.push(node);
16528 					}
16529 
16530 					node = node.firstChild;
16531 				}
16532 
16533 				return true;
16534 			};
16535 			
16536 			// Returns any parent caret container element
16537 			function getParentCaretContainer(node) {
16538 				while (node) {
16539 					if (node.id === caretContainerId) {
16540 						return node;
16541 					}
16542 
16543 					node = node.parentNode;
16544 				}
16545 			};
16546 
16547 			// Finds the first text node in the specified node
16548 			function findFirstTextNode(node) {
16549 				var walker;
16550 
16551 				if (node) {
16552 					walker = new TreeWalker(node, node);
16553 
16554 					for (node = walker.current(); node; node = walker.next()) {
16555 						if (node.nodeType === 3) {
16556 							return node;
16557 						}
16558 					}
16559 				}
16560 			};
16561 
16562 			// Removes the caret container for the specified node or all on the current document
16563 			function removeCaretContainer(node, move_caret) {
16564 				var child, rng;
16565 
16566 				if (!node) {
16567 					node = getParentCaretContainer(selection.getStart());
16568 
16569 					if (!node) {
16570 						while (node = dom.get(caretContainerId)) {
16571 							removeCaretContainer(node, false);
16572 						}
16573 					}
16574 				} else {
16575 					rng = selection.getRng(true);
16576 
16577 					if (isCaretContainerEmpty(node)) {
16578 						if (move_caret !== false) {
16579 							rng.setStartBefore(node);
16580 							rng.setEndBefore(node);
16581 						}
16582 
16583 						dom.remove(node);
16584 					} else {
16585 						child = findFirstTextNode(node);
16586 
16587 						if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
16588 							child = child.deleteData(0, 1);
16589 						}
16590 
16591 						dom.remove(node, 1);
16592 					}
16593 
16594 					selection.setRng(rng);
16595 				}
16596 			};
16597 			
16598 			// Applies formatting to the caret postion
16599 			function applyCaretFormat() {
16600 				var rng, caretContainer, textNode, offset, bookmark, container, text;
16601 
16602 				rng = selection.getRng(true);
16603 				offset = rng.startOffset;
16604 				container = rng.startContainer;
16605 				text = container.nodeValue;
16606 
16607 				caretContainer = getParentCaretContainer(selection.getStart());
16608 				if (caretContainer) {
16609 					textNode = findFirstTextNode(caretContainer);
16610 				}
16611 
16612 				// Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
16613 				if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
16614 					// Get bookmark of caret position
16615 					bookmark = selection.getBookmark();
16616 
16617 					// Collapse bookmark range (WebKit)
16618 					rng.collapse(true);
16619 
16620 					// Expand the range to the closest word and split it at those points
16621 					rng = expandRng(rng, get(name));
16622 					rng = rangeUtils.split(rng);
16623 
16624 					// Apply the format to the range
16625 					apply(name, vars, rng);
16626 
16627 					// Move selection back to caret position
16628 					selection.moveToBookmark(bookmark);
16629 				} else {
16630 					if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
16631 						caretContainer = createCaretContainer(true);
16632 						textNode = caretContainer.firstChild;
16633 
16634 						rng.insertNode(caretContainer);
16635 						offset = 1;
16636 
16637 						apply(name, vars, caretContainer);
16638 					} else {
16639 						apply(name, vars, caretContainer);
16640 					}
16641 
16642 					// Move selection to text node
16643 					selection.setCursorLocation(textNode, offset);
16644 				}
16645 			};
16646 
16647 			function removeCaretFormat() {
16648 				var rng = selection.getRng(true), container, offset, bookmark,
16649 					hasContentAfter, node, formatNode, parents = [], i, caretContainer;
16650 
16651 				container = rng.startContainer;
16652 				offset = rng.startOffset;
16653 				node = container;
16654 
16655 				if (container.nodeType == 3) {
16656 					if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {
16657 						hasContentAfter = true;
16658 					}
16659 
16660 					node = node.parentNode;
16661 				}
16662 
16663 				while (node) {
16664 					if (matchNode(node, name, vars)) {
16665 						formatNode = node;
16666 						break;
16667 					}
16668 
16669 					if (node.nextSibling) {
16670 						hasContentAfter = true;
16671 					}
16672 
16673 					parents.push(node);
16674 					node = node.parentNode;
16675 				}
16676 
16677 				// Node doesn't have the specified format
16678 				if (!formatNode) {
16679 					return;
16680 				}
16681 
16682 				// Is there contents after the caret then remove the format on the element
16683 				if (hasContentAfter) {
16684 					// Get bookmark of caret position
16685 					bookmark = selection.getBookmark();
16686 
16687 					// Collapse bookmark range (WebKit)
16688 					rng.collapse(true);
16689 
16690 					// Expand the range to the closest word and split it at those points
16691 					rng = expandRng(rng, get(name), true);
16692 					rng = rangeUtils.split(rng);
16693 
16694 					// Remove the format from the range
16695 					remove(name, vars, rng);
16696 
16697 					// Move selection back to caret position
16698 					selection.moveToBookmark(bookmark);
16699 				} else {
16700 					caretContainer = createCaretContainer();
16701 
16702 					node = caretContainer;
16703 					for (i = parents.length - 1; i >= 0; i--) {
16704 						node.appendChild(dom.clone(parents[i], false));
16705 						node = node.firstChild;
16706 					}
16707 
16708 					// Insert invisible character into inner most format element
16709 					node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
16710 					node = node.firstChild;
16711 
16712 					// Insert caret container after the formated node
16713 					dom.insertAfter(caretContainer, formatNode);
16714 
16715 					// Move selection to text node
16716 					selection.setCursorLocation(node, 1);
16717 				}
16718 			};
16719 
16720 			// Checks if the parent caret container node isn't empty if that is the case it
16721 			// will remove the bogus state on all children that isn't empty
16722 			function unmarkBogusCaretParents() {
16723 				var i, caretContainer, node;
16724 
16725 				caretContainer = getParentCaretContainer(selection.getStart());
16726 				if (caretContainer && !dom.isEmpty(caretContainer)) {
16727 					tinymce.walk(caretContainer, function(node) {
16728 						if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
16729 							dom.setAttrib(node, 'data-mce-bogus', null);
16730 						}
16731 					}, 'childNodes');
16732 				}
16733 			};
16734 
16735 			// Only bind the caret events once
16736 			if (!self._hasCaretEvents) {
16737 				// Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
16738 				ed.onBeforeGetContent.addToTop(function() {
16739 					var nodes = [], i;
16740 
16741 					if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
16742 						// Mark children
16743 						i = nodes.length;
16744 						while (i--) {
16745 							dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
16746 						}
16747 					}
16748 				});
16749 
16750 				// Remove caret container on mouse up and on key up
16751 				tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
16752 					ed[name].addToTop(function() {
16753 						removeCaretContainer();
16754 						unmarkBogusCaretParents();
16755 					});
16756 				});
16757 
16758 				// Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
16759 				ed.onKeyDown.addToTop(function(ed, e) {
16760 					var keyCode = e.keyCode;
16761 
16762 					if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
16763 						removeCaretContainer(getParentCaretContainer(selection.getStart()));
16764 					}
16765 
16766 					unmarkBogusCaretParents();
16767 				});
16768 
16769 				// Remove bogus state if they got filled by contents using editor.selection.setContent
16770 				selection.onSetContent.add(unmarkBogusCaretParents);
16771 
16772 				self._hasCaretEvents = true;
16773 			}
16774 
16775 			// Do apply or remove caret format
16776 			if (type == "apply") {
16777 				applyCaretFormat();
16778 			} else {
16779 				removeCaretFormat();
16780 			}
16781 		};
16782 
16783 		function moveStart(rng) {
16784 			var container = rng.startContainer,
16785 					offset = rng.startOffset, isAtEndOfText,
16786 					walker, node, nodes, tmpNode;
16787 
16788 			// Convert text node into index if possible
16789 			if (container.nodeType == 3 && offset >= container.nodeValue.length) {
16790 				// Get the parent container location and walk from there
16791 				offset = nodeIndex(container);
16792 				container = container.parentNode;
16793 				isAtEndOfText = true;
16794 			}
16795 
16796 			// Move startContainer/startOffset in to a suitable node
16797 			if (container.nodeType == 1) {
16798 				nodes = container.childNodes;
16799 				container = nodes[Math.min(offset, nodes.length - 1)];
16800 				walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
16801 
16802 				// If offset is at end of the parent node walk to the next one
16803 				if (offset > nodes.length - 1 || isAtEndOfText)
16804 					walker.next();
16805 
16806 				for (node = walker.current(); node; node = walker.next()) {
16807 					if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
16808 						// IE has a "neat" feature where it moves the start node into the closest element
16809 						// we can avoid this by inserting an element before it and then remove it after we set the selection
16810 						tmpNode = dom.create('a', null, INVISIBLE_CHAR);
16811 						node.parentNode.insertBefore(tmpNode, node);
16812 
16813 						// Set selection and remove tmpNode
16814 						rng.setStart(node, 0);
16815 						selection.setRng(rng);
16816 						dom.remove(tmpNode);
16817 
16818 						return;
16819 					}
16820 				}
16821 			}
16822 		};
16823 	};
16824 })(tinymce);
16825 
16826 tinymce.onAddEditor.add(function(tinymce, ed) {
16827 	var filters, fontSizes, dom, settings = ed.settings;
16828 
16829 	function replaceWithSpan(node, styles) {
16830 		tinymce.each(styles, function(value, name) {
16831 			if (value)
16832 				dom.setStyle(node, name, value);
16833 		});
16834 
16835 		dom.rename(node, 'span');
16836 	};
16837 
16838 	function convert(editor, params) {
16839 		dom = editor.dom;
16840 
16841 		if (settings.convert_fonts_to_spans) {
16842 			tinymce.each(dom.select('font,u,strike', params.node), function(node) {
16843 				filters[node.nodeName.toLowerCase()](ed.dom, node);
16844 			});
16845 		}
16846 	};
16847 
16848 	if (settings.inline_styles) {
16849 		fontSizes = tinymce.explode(settings.font_size_legacy_values);
16850 
16851 		filters = {
16852 			font : function(dom, node) {
16853 				replaceWithSpan(node, {
16854 					backgroundColor : node.style.backgroundColor,
16855 					color : node.color,
16856 					fontFamily : node.face,
16857 					fontSize : fontSizes[parseInt(node.size, 10) - 1]
16858 				});
16859 			},
16860 
16861 			u : function(dom, node) {
16862 				replaceWithSpan(node, {
16863 					textDecoration : 'underline'
16864 				});
16865 			},
16866 
16867 			strike : function(dom, node) {
16868 				replaceWithSpan(node, {
16869 					textDecoration : 'line-through'
16870 				});
16871 			}
16872 		};
16873 
16874 		ed.onPreProcess.add(convert);
16875 		ed.onSetContent.add(convert);
16876 
16877 		ed.onInit.add(function() {
16878 			ed.selection.onSetContent.add(convert);
16879 		});
16880 	}
16881 });
16882 
16883 (function(tinymce) {
16884 	var TreeWalker = tinymce.dom.TreeWalker;
16885 
16886 	tinymce.EnterKey = function(editor) {
16887 		var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements();
16888 
16889 		function handleEnterKey(evt) {
16890 			var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode,
16891 				newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
16892 
16893 			// Returns true if the block can be split into two blocks or not
16894 			function canSplitBlock(node) {
16895 				return node &&
16896 					dom.isBlock(node) &&
16897 					!/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
16898 					!/^(fixed|absolute)/i.test(node.style.position) && 
16899 					dom.getContentEditable(node) !== "true";
16900 			};
16901 
16902 			// Renders empty block on IE
16903 			function renderBlockOnIE(block) {
16904 				var oldRng;
16905 
16906 				if (tinymce.isIE && dom.isBlock(block)) {
16907 					oldRng = selection.getRng();
16908 					block.appendChild(dom.create('span', null, '\u00a0'));
16909 					selection.select(block);
16910 					block.lastChild.outerHTML = '';
16911 					selection.setRng(oldRng);
16912 				}
16913 			};
16914 
16915 			// Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
16916 			function trimInlineElementsOnLeftSideOfBlock(block) {
16917 				var node = block, firstChilds = [], i;
16918 
16919 				// Find inner most first child ex: <p><i><b>*</b></i></p>
16920 				while (node = node.firstChild) {
16921 					if (dom.isBlock(node)) {
16922 						return;
16923 					}
16924 
16925 					if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
16926 						firstChilds.push(node);
16927 					}
16928 				}
16929 
16930 				i = firstChilds.length;
16931 				while (i--) {
16932 					node = firstChilds[i];
16933 					if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
16934 						dom.remove(node);
16935 					}
16936 				}
16937 			};
16938 			
16939 			// Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image
16940 			function moveToCaretPosition(root) {
16941 				var walker, node, rng, y, viewPort, lastNode = root, tempElm;
16942 
16943 				rng = dom.createRng();
16944 
16945 				if (root.hasChildNodes()) {
16946 					walker = new TreeWalker(root, root);
16947 
16948 					while (node = walker.current()) {
16949 						if (node.nodeType == 3) {
16950 							rng.setStart(node, 0);
16951 							rng.setEnd(node, 0);
16952 							break;
16953 						}
16954 
16955 						if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
16956 							rng.setStartBefore(node);
16957 							rng.setEndBefore(node);
16958 							break;
16959 						}
16960 
16961 						lastNode = node;
16962 						node = walker.next();
16963 					}
16964 
16965 					if (!node) {
16966 						rng.setStart(lastNode, 0);
16967 						rng.setEnd(lastNode, 0);
16968 					}
16969 				} else {
16970 					if (root.nodeName == 'BR') {
16971 						if (root.nextSibling && dom.isBlock(root.nextSibling)) {
16972 							// Trick on older IE versions to render the caret before the BR between two lists
16973 							if (!documentMode || documentMode < 9) {
16974 								tempElm = dom.create('br');
16975 								root.parentNode.insertBefore(tempElm, root);
16976 							}
16977 
16978 							rng.setStartBefore(root);
16979 							rng.setEndBefore(root);
16980 						} else {
16981 							rng.setStartAfter(root);
16982 							rng.setEndAfter(root);
16983 						}
16984 					} else {
16985 						rng.setStart(root, 0);
16986 						rng.setEnd(root, 0);
16987 					}
16988 				}
16989 
16990 				selection.setRng(rng);
16991 
16992 				// Remove tempElm created for old IE:s
16993 				dom.remove(tempElm);
16994 
16995 				viewPort = dom.getViewPort(editor.getWin());
16996 
16997 				// scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
16998 				y = dom.getPos(root).y;
16999 				if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {
17000 					editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
17001 				}
17002 			};
17003 
17004 			// Creates a new block element by cloning the current one or creating a new one if the name is specified
17005 			// This function will also copy any text formatting from the parent block and add it to the new one
17006 			function createNewBlock(name) {
17007 				var node = container, block, clonedNode, caretNode;
17008 
17009 				block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false);
17010 				caretNode = block;
17011 
17012 				// Clone any parent styles
17013 				if (settings.keep_styles !== false) {
17014 					do {
17015 						if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
17016 							clonedNode = node.cloneNode(false);
17017 							dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
17018 
17019 							if (block.hasChildNodes()) {
17020 								clonedNode.appendChild(block.firstChild);
17021 								block.appendChild(clonedNode);
17022 							} else {
17023 								caretNode = clonedNode;
17024 								block.appendChild(clonedNode);
17025 							}
17026 						}
17027 					} while (node = node.parentNode);
17028 				}
17029 
17030 				// BR is needed in empty blocks on non IE browsers
17031 				if (!tinymce.isIE) {
17032 					caretNode.innerHTML = '<br>';
17033 				}
17034 
17035 				return block;
17036 			};
17037 
17038 			// Returns true/false if the caret is at the start/end of the parent block element
17039 			function isCaretAtStartOrEndOfBlock(start) {
17040 				var walker, node, name;
17041 
17042 				// Caret is in the middle of a text node like "a|b"
17043 				if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
17044 					return false;
17045 				}
17046 
17047 				// If after the last element in block node edge case for #5091
17048 				if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
17049 					return true;
17050 				}
17051 
17052 				// If the caret if before the first element in parentBlock
17053 				if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
17054 					return true;
17055 				}
17056 
17057 				// Caret can be before/after a table
17058 				if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
17059 					return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
17060 				}
17061 
17062 				// Walk the DOM and look for text nodes or non empty elements
17063 				walker = new TreeWalker(container, parentBlock);
17064 	
17065 				// If caret is in beginning or end of a text block then jump to the next/previous node
17066 				if (container.nodeType == 3) {
17067 					if (start && offset == 0) {
17068 						walker.prev();
17069 					} else if (!start && offset == container.nodeValue.length) {
17070 						walker.next();
17071 					}
17072 				}
17073 
17074 				while (node = walker.current()) {
17075 					if (node.nodeType === 1) {
17076 						// Ignore bogus elements
17077 						if (!node.getAttribute('data-mce-bogus')) {
17078 							// Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
17079 							name = node.nodeName.toLowerCase();
17080 							if (nonEmptyElementsMap[name] && name !== 'br') {
17081 								return false;
17082 							}
17083 						}
17084 					} else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
17085 						return false;
17086 					}
17087 
17088 					if (start) {
17089 						walker.prev();
17090 					} else {
17091 						walker.next();
17092 					}
17093 				}
17094 
17095 				return true;
17096 			};
17097 
17098 			// Wraps any text nodes or inline elements in the specified forced root block name
17099 			function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
17100 				var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P';
17101 
17102 				// Not in a block element or in a table cell or caption
17103 				parentBlock = dom.getParent(container, dom.isBlock);
17104 				if (!parentBlock || !canSplitBlock(parentBlock)) {
17105 					parentBlock = parentBlock || editableRoot;
17106 
17107 					if (!parentBlock.hasChildNodes()) {
17108 						newBlock = dom.create(blockName);
17109 						parentBlock.appendChild(newBlock);
17110 						rng.setStart(newBlock, 0);
17111 						rng.setEnd(newBlock, 0);
17112 						return newBlock;
17113 					}
17114 
17115 					// Find parent that is the first child of parentBlock
17116 					node = container;
17117 					while (node.parentNode != parentBlock) {
17118 						node = node.parentNode;
17119 					}
17120 
17121 					// Loop left to find start node start wrapping at
17122 					while (node && !dom.isBlock(node)) {
17123 						startNode = node;
17124 						node = node.previousSibling;
17125 					}
17126 
17127 					if (startNode) {
17128 						newBlock = dom.create(blockName);
17129 						startNode.parentNode.insertBefore(newBlock, startNode);
17130 
17131 						// Start wrapping until we hit a block
17132 						node = startNode;
17133 						while (node && !dom.isBlock(node)) {
17134 							next = node.nextSibling;
17135 							newBlock.appendChild(node);
17136 							node = next;
17137 						}
17138 
17139 						// Restore range to it's past location
17140 						rng.setStart(container, offset);
17141 						rng.setEnd(container, offset);
17142 					}
17143 				}
17144 
17145 				return container;
17146 			};
17147 
17148 			// Inserts a block or br before/after or in the middle of a split list of the LI is empty
17149 			function handleEmptyListItem() {
17150 				function isFirstOrLastLi(first) {
17151 					var node = containerBlock[first ? 'firstChild' : 'lastChild'];
17152 
17153 					// Find first/last element since there might be whitespace there
17154 					while (node) {
17155 						if (node.nodeType == 1) {
17156 							break;
17157 						}
17158 
17159 						node = node[first ? 'nextSibling' : 'previousSibling'];
17160 					}
17161 
17162 					return node === parentBlock;
17163 				};
17164 
17165 				newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
17166 
17167 				if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
17168 					// Is first and last list item then replace the OL/UL with a text block
17169 					dom.replace(newBlock, containerBlock);
17170 				} else if (isFirstOrLastLi(true)) {
17171 					// First LI in list then remove LI and add text block before list
17172 					containerBlock.parentNode.insertBefore(newBlock, containerBlock);
17173 				} else if (isFirstOrLastLi()) {
17174 					// Last LI in list then temove LI and add text block after list
17175 					dom.insertAfter(newBlock, containerBlock);
17176 					renderBlockOnIE(newBlock);
17177 				} else {
17178 					// Middle LI in list the split the list and insert a text block in the middle
17179 					// Extract after fragment and insert it after the current block
17180 					tmpRng = rng.cloneRange();
17181 					tmpRng.setStartAfter(parentBlock);
17182 					tmpRng.setEndAfter(containerBlock);
17183 					fragment = tmpRng.extractContents();
17184 					dom.insertAfter(fragment, containerBlock);
17185 					dom.insertAfter(newBlock, containerBlock);
17186 				}
17187 
17188 				dom.remove(parentBlock);
17189 				moveToCaretPosition(newBlock);
17190 				undoManager.add();
17191 			};
17192 
17193 			// Walks the parent block to the right and look for BR elements
17194 			function hasRightSideBr() {
17195 				var walker = new TreeWalker(container, parentBlock), node;
17196 
17197 				while (node = walker.current()) {
17198 					if (node.nodeName == 'BR') {
17199 						return true;
17200 					}
17201 
17202 					node = walker.next();
17203 				}
17204 			}
17205 			
17206 			// Inserts a BR element if the forced_root_block option is set to false or empty string
17207 			function insertBr() {
17208 				var brElm, extraBr;
17209 
17210 				if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
17211 					// Insert extra BR element at the end block elements
17212 					if (!tinymce.isIE && !hasRightSideBr()) {
17213 						brElm = dom.create('br')
17214 						rng.insertNode(brElm);
17215 						rng.setStartAfter(brElm);
17216 						rng.setEndAfter(brElm);
17217 						extraBr = true;
17218 					}
17219 				}
17220 
17221 				brElm = dom.create('br');
17222 				rng.insertNode(brElm);
17223 
17224 				// Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
17225 				if (tinymce.isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
17226 					brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
17227 				}
17228 
17229 				if (!extraBr) {
17230 					rng.setStartAfter(brElm);
17231 					rng.setEndAfter(brElm);
17232 				} else {
17233 					rng.setStartBefore(brElm);
17234 					rng.setEndBefore(brElm);
17235 				}
17236 
17237 				selection.setRng(rng);
17238 				undoManager.add();
17239 			};
17240 
17241 			// Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
17242 			function trimLeadingLineBreaks(node) {
17243 				do {
17244 					if (node.nodeType === 3) {
17245 						node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
17246 					}
17247 
17248 					node = node.firstChild;
17249 				} while (node);
17250 			};
17251 
17252 			function getEditableRoot(node) {
17253 				var root = dom.getRoot(), parent, editableRoot;
17254 
17255 				// Get all parents until we hit a non editable parent or the root
17256 				parent = node;
17257 				while (parent !== root && dom.getContentEditable(parent) !== "false") {
17258 					if (dom.getContentEditable(parent) === "true") {
17259 						editableRoot = parent;
17260 					}
17261 
17262 					parent = parent.parentNode;
17263 				}
17264 				
17265 				return parent !== root ? editableRoot : root;
17266 			};
17267 
17268 			// Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block
17269 			function addBrToBlockIfNeeded(block) {
17270 				var lastChild;
17271 
17272 				// IE will render the blocks correctly other browsers needs a BR
17273 				if (!tinymce.isIE) {
17274 					block.normalize(); // Remove empty text nodes that got left behind by the extract
17275 
17276 					// Check if the block is empty or contains a floated last child
17277 					lastChild = block.lastChild;
17278 					if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
17279 						dom.add(block, 'br');
17280 					}
17281 				}
17282 			};
17283 
17284 			// Delete any selected contents
17285 			if (!rng.collapsed) {
17286 				editor.execCommand('Delete');
17287 				return;
17288 			}
17289 
17290 			// Event is blocked by some other handler for example the lists plugin
17291 			if (evt.isDefaultPrevented()) {
17292 				return;
17293 			}
17294 
17295 			// Setup range items and newBlockName
17296 			container = rng.startContainer;
17297 			offset = rng.startOffset;
17298 			newBlockName = settings.forced_root_block;
17299 			newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
17300 			documentMode = dom.doc.documentMode;
17301 
17302 			// Resolve node index
17303 			if (container.nodeType == 1 && container.hasChildNodes()) {
17304 				isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
17305 				container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
17306 				if (isAfterLastNodeInContainer && container.nodeType == 3) {
17307 					offset = container.nodeValue.length;
17308 				} else {
17309 					offset = 0;
17310 				}
17311 			}
17312 
17313 			// Get editable root node normaly the body element but sometimes a div or span
17314 			editableRoot = getEditableRoot(container);
17315 
17316 			// If there is no editable root then enter is done inside a contentEditable false element
17317 			if (!editableRoot) {
17318 				return;
17319 			}
17320 
17321 			undoManager.beforeChange();
17322 
17323 			// If editable root isn't block nor the root of the editor
17324 			if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
17325 				if (!newBlockName || evt.shiftKey) {
17326 					insertBr();
17327 				}
17328 
17329 				return;
17330 			}
17331 
17332 			// Wrap the current node and it's sibling in a default block if it's needed.
17333 			// for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
17334 			// This won't happen if root blocks are disabled or the shiftKey is pressed
17335 			if ((newBlockName && !evt.shiftKey) || (!newBlockName && evt.shiftKey)) {
17336 				container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
17337 			}
17338 
17339 			// Find parent block and setup empty block paddings
17340 			parentBlock = dom.getParent(container, dom.isBlock);
17341 			containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
17342 
17343 			// Setup block names
17344 			parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
17345 			containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
17346 
17347 			// Handle enter inside an empty list item
17348 			if (parentBlockName == 'LI' && dom.isEmpty(parentBlock)) {
17349 				// Let the list plugin or browser handle nested lists for now
17350 				if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) {
17351 					return false;
17352 				}
17353 
17354 				handleEmptyListItem();
17355 				return;
17356 			}
17357 
17358 			// Don't split PRE tags but insert a BR instead easier when writing code samples etc
17359 			if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
17360 				if (!evt.shiftKey) {
17361 					insertBr();
17362 					return;
17363 				}
17364 			} else {
17365 				// If no root block is configured then insert a BR by default or if the shiftKey is pressed
17366 				if ((!newBlockName && !evt.shiftKey && parentBlockName != 'LI') || (newBlockName && evt.shiftKey)) {
17367 					insertBr();
17368 					return;
17369 				}
17370 			}
17371 
17372 			// Default block name if it's not configured
17373 			newBlockName = newBlockName || 'P';
17374 
17375 			// Insert new block before/after the parent block depending on caret location
17376 			if (isCaretAtStartOrEndOfBlock()) {
17377 				// If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
17378 				if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
17379 					newBlock = createNewBlock(newBlockName);
17380 				} else {
17381 					newBlock = createNewBlock();
17382 				}
17383 
17384 				// Split the current container block element if enter is pressed inside an empty inner block element
17385 				if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
17386 					// Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
17387 					newBlock = dom.split(containerBlock, parentBlock);
17388 				} else {
17389 					dom.insertAfter(newBlock, parentBlock);
17390 				}
17391 
17392 				moveToCaretPosition(newBlock);
17393 			} else if (isCaretAtStartOrEndOfBlock(true)) {
17394 				// Insert new block before
17395 				newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
17396 				renderBlockOnIE(newBlock);
17397 			} else {
17398 				// Extract after fragment and insert it after the current block
17399 				tmpRng = rng.cloneRange();
17400 				tmpRng.setEndAfter(parentBlock);
17401 				fragment = tmpRng.extractContents();
17402 				trimLeadingLineBreaks(fragment);
17403 				newBlock = fragment.firstChild;
17404 				dom.insertAfter(fragment, parentBlock);
17405 				trimInlineElementsOnLeftSideOfBlock(newBlock);
17406 				addBrToBlockIfNeeded(parentBlock);
17407 				moveToCaretPosition(newBlock);
17408 			}
17409 
17410 			dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
17411 			undoManager.add();
17412 		}
17413 
17414 		editor.onKeyDown.add(function(ed, evt) {
17415 			if (evt.keyCode == 13) {
17416 				if (handleEnterKey(evt) !== false) {
17417 					evt.preventDefault();
17418 				}
17419 			}
17420 		});
17421 	};
17422 })(tinymce);
17423 
17424